From ea582dc65029f5537d6093c3fadb8b90b7768f91 Mon Sep 17 00:00:00 2001 From: Jared Burgett <147995946+jaredburgettelastic@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:56:08 -0500 Subject: [PATCH 01/84] Flipped Security Entity Store flag to being a "disable" flag (#195818) ## Summary The Security Solution Entity Store feature will now be available by default. However, there will be a flag that can be switched on, if desired, to **disable** that feature entirely. Regardless of whether this flag is enabled or not, Security's Entity Store is still only fully enabled through an enablement workflow. In other words, a Security Solution customer must turn on the feature through an onboarding workflow in order to enable its features. Additionally, we are disabling this feature in Serverless at first, to perform proper Serverless load/performance testing. (We do not expect it to be significantly different than ESS/ECH, but are doing so out of an abundance of caution). --------- Co-authored-by: Pablo Machado --- config/serverless.security.yml | 5 +++++ .../security_solution/common/experimental_features.ts | 5 +++-- .../entity_analytics/pages/entity_analytics_dashboard.tsx | 4 ++-- .../lib/entity_analytics/register_entity_analytics_routes.ts | 2 +- x-pack/plugins/security_solution/server/plugin.ts | 2 +- .../test_suites/task_manager/check_registered_task_types.ts | 1 + .../trial_license_complete_tier/configs/ess.config.ts | 5 +---- .../trial_license_complete_tier/configs/serverless.config.ts | 1 - .../entity_store/trial_license_complete_tier/engine.ts | 3 ++- .../trial_license_complete_tier/entities_list.ts | 3 ++- 10 files changed, 18 insertions(+), 13 deletions(-) diff --git a/config/serverless.security.yml b/config/serverless.security.yml index 9244b51702f9c..fe86a864d5cf3 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -121,3 +121,8 @@ console.ui.embeddedEnabled: false # Enable project level rentention checks in DSL form from Index Management UI xpack.index_management.enableProjectLevelRetentionChecks: true + +# Experimental Security Solution features + +# This feature is disabled in Serverless until fully performance tested within a Serverless environment +xpack.securitySolution.enableExperimental: ['entityStoreDisabled'] diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 1e5ffee50afc7..f18ddff6e4f17 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -236,9 +236,10 @@ export const allowedExperimentalValues = Object.freeze({ dataIngestionHubEnabled: false, /** - * Enables the new Entity Store engine routes + * Disables Security's Entity Store engine routes. The Entity Store feature is available by default, but + * can be disabled if necessary in a given environment. */ - entityStoreEnabled: false, + entityStoreDisabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx index 48d2911e7c36a..90f5ec66c8a38 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx @@ -32,7 +32,7 @@ const EntityAnalyticsComponent = () => { const { indicesExist, loading: isSourcererLoading, sourcererDataView } = useSourcererDataView(); const isRiskScoreModuleLicenseAvailable = useHasSecurityCapability('entity-analytics'); - const isEntityStoreEnabled = useIsExperimentalFeatureEnabled('entityStoreEnabled'); + const isEntityStoreDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); return ( <> @@ -71,7 +71,7 @@ const EntityAnalyticsComponent = () => { - {isEntityStoreEnabled ? ( + {!isEntityStoreDisabled ? ( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts index b4eb0d36e21fb..bd097e8641637 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts @@ -15,7 +15,7 @@ export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDe registerAssetCriticalityRoutes(routeDeps); registerRiskScoreRoutes(routeDeps); registerRiskEngineRoutes(routeDeps); - if (routeDeps.config.experimentalFeatures.entityStoreEnabled) { + if (!routeDeps.config.experimentalFeatures.entityStoreDisabled) { registerEntityStoreRoutes(routeDeps); } }; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 9203a068b278d..2ac776d37f1e5 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -221,7 +221,7 @@ export class Plugin implements ISecuritySolutionPlugin { logger.error(`Error scheduling entity analytics migration: ${err}`); }); - if (experimentalFeatures.entityStoreEnabled) { + if (!experimentalFeatures.entityStoreDisabled) { registerEntityStoreFieldRetentionEnrichTask({ getStartServices: core.getStartServices, logger: this.logger, diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index eeb8b6e3474c9..55856f3c80402 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -138,6 +138,7 @@ export default function ({ getService }: FtrProviderContext) { 'endpoint:complete-external-response-actions', 'endpoint:metadata-check-transforms-task', 'endpoint:user-artifact-packager', + 'entity_store:field_retention:enrichment', 'fleet:check-deleted-files-task', 'fleet:delete-unenrolled-agents-task', 'fleet:deploy_agent_policies', diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts index ba7a4c83e2ad7..9c168e481df2e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts @@ -15,10 +15,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), kbnTestServer: { ...functionalConfig.get('kbnTestServer'), - serverArgs: [ - ...functionalConfig.get('kbnTestServer.serverArgs'), - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['entityStoreEnabled'])}`, - ], + serverArgs: [...functionalConfig.get('kbnTestServer.serverArgs')], }, testFiles: [require.resolve('..')], junit: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts index 990bdd8778aeb..f447df7d83cbc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts @@ -9,7 +9,6 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ kbnTestServerArgs: [ - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['entityStoreEnabled'])}`, `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([ { product_line: 'security', product_tier: 'complete' }, { product_line: 'endpoint', product_tier: 'complete' }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts index d6963c28b2f73..6c41f4f916141 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts @@ -24,8 +24,9 @@ export default ({ getService }: FtrProviderContext) => { } = elasticAssetCheckerFactory(getService); const utils = EntityStoreUtils(getService); + // TODO: unskip once permissions issue is resolved - describe.skip('@ess @serverless @skipInServerlessMKI Entity Store Engine APIs', () => { + describe.skip('@ess Entity Store Engine APIs', () => { const dataView = dataViewRouteHelpersFactory(supertest); before(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entities_list.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entities_list.ts index 0a772f637ef55..69f9c14d06086 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entities_list.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entities_list.ts @@ -10,8 +10,9 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const securitySolutionApi = getService('securitySolutionApi'); + // TODO: unskip once permissions issue is resolved - describe.skip('@ess @serverless @skipInServerlessMKI Entity store - Entities list API', () => { + describe.skip('@ess Entity store - Entities list API', () => { describe('when the entity store is disable', () => { it("should return response with success status when the index doesn't exist", async () => { const { body } = await securitySolutionApi.listEntities({ From 68cceefbec943b316e43514248d75bc9b2ac6026 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:12:25 +1100 Subject: [PATCH 02/84] [api-docs] 2024-10-15 Daily api_docs build (#196232) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/861 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- .../ai_assistant_management_selection.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/apm_data_access.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_data_migration.mdx | 2 +- api_docs/cloud_defend.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/content_management.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_quality.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_usage.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/dataset_quality.mdx | 2 +- api_docs/deprecations_by_api.mdx | 4 +- api_docs/deprecations_by_plugin.mdx | 4 +- api_docs/deprecations_by_team.mdx | 4 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.devdocs.json | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/discover_shared.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/elastic_assistant.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/entities_data_access.mdx | 2 +- api_docs/entity_manager.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/esql.mdx | 2 +- api_docs/esql_data_grid.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_annotation_listing.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.devdocs.json | 406 ++++++++++++++++-- api_docs/features.mdx | 7 +- api_docs/field_formats.mdx | 2 +- api_docs/fields_metadata.devdocs.json | 37 ++ api_docs/fields_metadata.mdx | 4 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/inference.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/ingest_pipelines.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/integration_assistant.devdocs.json | 70 +-- api_docs/integration_assistant.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/inventory.mdx | 2 +- api_docs/investigate.mdx | 2 +- api_docs/investigate_app.mdx | 2 +- api_docs/kbn_actions_types.mdx | 2 +- api_docs/kbn_ai_assistant.mdx | 2 +- api_docs/kbn_ai_assistant_common.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_log_pattern_analysis.mdx | 2 +- api_docs/kbn_aiops_log_rate_analysis.mdx | 2 +- .../kbn_alerting_api_integration_helpers.mdx | 2 +- api_docs/kbn_alerting_comparators.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerting_types.mdx | 2 +- .../kbn_alerts_as_data_utils.devdocs.json | 30 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_grouping.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_collection_utils.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_data_view.mdx | 2 +- api_docs/kbn_apm_synthtrace.devdocs.json | 152 +++++++ api_docs/kbn_apm_synthtrace.mdx | 4 +- .../kbn_apm_synthtrace_client.devdocs.json | 248 +++++++++++ api_docs/kbn_apm_synthtrace_client.mdx | 4 +- api_docs/kbn_apm_types.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_avc_banner.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_bfetch_error.mdx | 2 +- api_docs/kbn_calculate_auto.mdx | 2 +- .../kbn_calculate_width_from_char_count.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cbor.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- .../kbn_cloud_security_posture.devdocs.json | 176 ++++---- api_docs/kbn_cloud_security_posture.mdx | 2 +- ...cloud_security_posture_common.devdocs.json | 220 +++++----- .../kbn_cloud_security_posture_common.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mock.mdx | 2 +- api_docs/kbn_code_owners.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_content_editor.mdx | 2 +- ...ent_management_content_insights_public.mdx | 2 +- ...ent_management_content_insights_server.mdx | 2 +- ...bn_content_management_favorites_public.mdx | 2 +- ...bn_content_management_favorites_server.mdx | 2 +- ...tent_management_tabbed_table_list_view.mdx | 2 +- ...kbn_content_management_table_list_view.mdx | 2 +- ...tent_management_table_list_view_common.mdx | 2 +- ...ntent_management_table_list_view_table.mdx | 2 +- .../kbn_content_management_user_profiles.mdx | 2 +- api_docs/kbn_content_management_utils.mdx | 2 +- .../kbn_core_analytics_browser.devdocs.json | 56 ++- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- .../kbn_core_analytics_server.devdocs.json | 56 ++- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_apps_server_internal.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.devdocs.json | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_custom_branding_browser.mdx | 2 +- ..._core_custom_branding_browser_internal.mdx | 2 +- ...kbn_core_custom_branding_browser_mocks.mdx | 2 +- api_docs/kbn_core_custom_branding_common.mdx | 2 +- api_docs/kbn_core_custom_branding_server.mdx | 2 +- ...n_core_custom_branding_server_internal.mdx | 2 +- .../kbn_core_custom_branding_server_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_feature_flags_browser.mdx | 2 +- ...bn_core_feature_flags_browser_internal.mdx | 2 +- .../kbn_core_feature_flags_browser_mocks.mdx | 2 +- api_docs/kbn_core_feature_flags_server.mdx | 2 +- ...kbn_core_feature_flags_server_internal.mdx | 2 +- .../kbn_core_feature_flags_server_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.devdocs.json | 98 +++-- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- .../kbn_core_plugins_contracts_browser.mdx | 2 +- .../kbn_core_plugins_contracts_server.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_security_browser.mdx | 2 +- .../kbn_core_security_browser_internal.mdx | 2 +- api_docs/kbn_core_security_browser_mocks.mdx | 2 +- api_docs/kbn_core_security_common.mdx | 2 +- api_docs/kbn_core_security_server.mdx | 2 +- .../kbn_core_security_server_internal.mdx | 2 +- api_docs/kbn_core_security_server_mocks.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- .../kbn_core_test_helpers_model_versions.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_browser.mdx | 2 +- ...kbn_core_user_profile_browser_internal.mdx | 2 +- .../kbn_core_user_profile_browser_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_common.mdx | 2 +- api_docs/kbn_core_user_profile_server.mdx | 2 +- .../kbn_core_user_profile_server_internal.mdx | 2 +- .../kbn_core_user_profile_server_mocks.mdx | 2 +- api_docs/kbn_core_user_settings_server.mdx | 2 +- .../kbn_core_user_settings_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_custom_icons.mdx | 2 +- api_docs/kbn_custom_integrations.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_data_forge.mdx | 2 +- api_docs/kbn_data_service.mdx | 2 +- api_docs/kbn_data_stream_adapter.mdx | 2 +- api_docs/kbn_data_view_utils.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_deeplinks_analytics.mdx | 2 +- api_docs/kbn_deeplinks_devtools.mdx | 2 +- api_docs/kbn_deeplinks_fleet.mdx | 2 +- api_docs/kbn_deeplinks_management.mdx | 2 +- api_docs/kbn_deeplinks_ml.mdx | 2 +- .../kbn_deeplinks_observability.devdocs.json | 32 +- api_docs/kbn_deeplinks_observability.mdx | 4 +- api_docs/kbn_deeplinks_search.mdx | 2 +- api_docs/kbn_deeplinks_security.mdx | 2 +- api_docs/kbn_deeplinks_shared.mdx | 2 +- api_docs/kbn_default_nav_analytics.mdx | 2 +- api_docs/kbn_default_nav_devtools.mdx | 2 +- api_docs/kbn_default_nav_management.mdx | 2 +- api_docs/kbn_default_nav_ml.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_discover_utils.devdocs.json | 51 ++- api_docs/kbn_discover_utils.mdx | 4 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_elastic_agent_utils.mdx | 2 +- api_docs/kbn_elastic_assistant.mdx | 2 +- api_docs/kbn_elastic_assistant_common.mdx | 2 +- api_docs/kbn_entities_schema.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_esql_ast.mdx | 2 +- api_docs/kbn_esql_editor.mdx | 2 +- api_docs/kbn_esql_utils.devdocs.json | 53 +++ api_docs/kbn_esql_utils.mdx | 4 +- api_docs/kbn_esql_validation_autocomplete.mdx | 2 +- api_docs/kbn_event_annotation_common.mdx | 2 +- api_docs/kbn_event_annotation_components.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_field_utils.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- api_docs/kbn_formatters.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- .../kbn_ftr_common_functional_ui_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_console_definitions.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_grid_layout.mdx | 2 +- api_docs/kbn_grouping.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- .../kbn_index_management_shared_types.mdx | 2 +- api_docs/kbn_inference_integration_flyout.mdx | 2 +- api_docs/kbn_infra_forge.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_investigation_shared.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_ipynb.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_json_schemas.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_language_documentation.mdx | 2 +- api_docs/kbn_lens_embeddable_utils.mdx | 2 +- api_docs/kbn_lens_formula_docs.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_content_badge.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_management_cards_navigation.mdx | 2 +- .../kbn_management_settings_application.mdx | 2 +- ...ent_settings_components_field_category.mdx | 2 +- ...gement_settings_components_field_input.mdx | 2 +- ...nagement_settings_components_field_row.mdx | 2 +- ...bn_management_settings_components_form.mdx | 2 +- ...n_management_settings_field_definition.mdx | 2 +- api_docs/kbn_management_settings_ids.mdx | 2 +- ...n_management_settings_section_registry.mdx | 2 +- api_docs/kbn_management_settings_types.mdx | 2 +- .../kbn_management_settings_utilities.mdx | 2 +- api_docs/kbn_management_storybook_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_maps_vector_tile_utils.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_anomaly_utils.mdx | 2 +- api_docs/kbn_ml_cancellable_search.mdx | 2 +- api_docs/kbn_ml_category_validator.mdx | 2 +- api_docs/kbn_ml_chi2test.mdx | 2 +- .../kbn_ml_data_frame_analytics_utils.mdx | 2 +- api_docs/kbn_ml_data_grid.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_date_utils.mdx | 2 +- api_docs/kbn_ml_error_utils.mdx | 2 +- .../kbn_ml_field_stats_flyout.devdocs.json | 136 +++--- api_docs/kbn_ml_field_stats_flyout.mdx | 4 +- api_docs/kbn_ml_in_memory_table.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_kibana_theme.mdx | 2 +- api_docs/kbn_ml_local_storage.mdx | 2 +- api_docs/kbn_ml_nested_property.mdx | 2 +- api_docs/kbn_ml_number_utils.mdx | 2 +- api_docs/kbn_ml_parse_interval.mdx | 2 +- api_docs/kbn_ml_query_utils.mdx | 2 +- api_docs/kbn_ml_random_sampler_utils.mdx | 2 +- api_docs/kbn_ml_route_utils.mdx | 2 +- api_docs/kbn_ml_runtime_field_utils.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_ml_time_buckets.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_ui_actions.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_ml_validators.mdx | 2 +- api_docs/kbn_mock_idp_utils.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_object_versioning_utils.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- .../kbn_observability_alerting_rule_utils.mdx | 2 +- .../kbn_observability_alerting_test_data.mdx | 2 +- ...ility_get_padded_alert_time_range_util.mdx | 2 +- api_docs/kbn_observability_logs_overview.mdx | 2 +- ...kbn_observability_synthetics_test_data.mdx | 2 +- api_docs/kbn_openapi_bundler.mdx | 2 +- api_docs/kbn_openapi_generator.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- api_docs/kbn_panel_loader.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_check.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_presentation_containers.mdx | 2 +- api_docs/kbn_presentation_publishing.mdx | 2 +- api_docs/kbn_product_doc_artifact_builder.mdx | 2 +- api_docs/kbn_profiling_utils.mdx | 2 +- api_docs/kbn_random_sampling.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_react_hooks.mdx | 2 +- api_docs/kbn_react_kibana_context_common.mdx | 2 +- api_docs/kbn_react_kibana_context_render.mdx | 2 +- api_docs/kbn_react_kibana_context_root.mdx | 2 +- api_docs/kbn_react_kibana_context_styled.mdx | 2 +- api_docs/kbn_react_kibana_context_theme.mdx | 2 +- api_docs/kbn_react_kibana_mount.mdx | 2 +- api_docs/kbn_recently_accessed.mdx | 2 +- api_docs/kbn_repo_file_maps.mdx | 2 +- api_docs/kbn_repo_linter.mdx | 2 +- api_docs/kbn_repo_path.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_reporting_common.mdx | 2 +- api_docs/kbn_reporting_csv_share_panel.mdx | 2 +- api_docs/kbn_reporting_export_types_csv.mdx | 2 +- .../kbn_reporting_export_types_csv_common.mdx | 2 +- api_docs/kbn_reporting_export_types_pdf.mdx | 2 +- .../kbn_reporting_export_types_pdf_common.mdx | 2 +- api_docs/kbn_reporting_export_types_png.mdx | 2 +- .../kbn_reporting_export_types_png_common.mdx | 2 +- api_docs/kbn_reporting_mocks_server.mdx | 2 +- api_docs/kbn_reporting_public.mdx | 2 +- api_docs/kbn_reporting_server.mdx | 2 +- api_docs/kbn_resizable_layout.mdx | 2 +- .../kbn_response_ops_feature_flag_service.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_rollup.mdx | 2 +- api_docs/kbn_router_to_openapispec.mdx | 2 +- api_docs/kbn_router_utils.mdx | 2 +- api_docs/kbn_rrule.mdx | 2 +- api_docs/kbn_rule_data_utils.devdocs.json | 17 +- api_docs/kbn_rule_data_utils.mdx | 4 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_screenshotting_server.mdx | 2 +- api_docs/kbn_search_api_keys_components.mdx | 2 +- api_docs/kbn_search_api_keys_server.mdx | 2 +- api_docs/kbn_search_api_panels.mdx | 2 +- api_docs/kbn_search_connectors.mdx | 2 +- api_docs/kbn_search_errors.mdx | 2 +- api_docs/kbn_search_index_documents.mdx | 2 +- api_docs/kbn_search_response_warnings.mdx | 2 +- api_docs/kbn_search_shared_ui.mdx | 2 +- api_docs/kbn_search_types.mdx | 2 +- api_docs/kbn_security_api_key_management.mdx | 2 +- ...n_security_authorization_core.devdocs.json | 188 ++++---- api_docs/kbn_security_authorization_core.mdx | 4 +- ...ity_authorization_core_common.devdocs.json | 102 +++++ ...kbn_security_authorization_core_common.mdx | 30 ++ api_docs/kbn_security_form_components.mdx | 2 +- api_docs/kbn_security_hardening.mdx | 2 +- ..._security_plugin_types_common.devdocs.json | 104 +++++ api_docs/kbn_security_plugin_types_common.mdx | 4 +- ..._security_plugin_types_public.devdocs.json | 8 +- api_docs/kbn_security_plugin_types_public.mdx | 2 +- api_docs/kbn_security_plugin_types_server.mdx | 2 +- ...ecurity_role_management_model.devdocs.json | 24 +- .../kbn_security_role_management_model.mdx | 4 +- api_docs/kbn_security_solution_common.mdx | 2 +- ...kbn_security_solution_distribution_bar.mdx | 2 +- ...bn_security_solution_features.devdocs.json | 22 +- api_docs/kbn_security_solution_features.mdx | 2 +- api_docs/kbn_security_solution_navigation.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- api_docs/kbn_security_ui_components.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- .../kbn_server_route_repository_client.mdx | 2 +- .../kbn_server_route_repository_utils.mdx | 2 +- api_docs/kbn_serverless_common_settings.mdx | 2 +- .../kbn_serverless_observability_settings.mdx | 2 +- api_docs/kbn_serverless_project_switcher.mdx | 2 +- api_docs/kbn_serverless_search_settings.mdx | 2 +- api_docs/kbn_serverless_security_settings.mdx | 2 +- api_docs/kbn_serverless_storybook_config.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- ...n_shared_ux_chrome_navigation.devdocs.json | 11 - api_docs/kbn_shared_ux_chrome_navigation.mdx | 4 +- api_docs/kbn_shared_ux_error_boundary.mdx | 2 +- api_docs/kbn_shared_ux_file_context.mdx | 2 +- api_docs/kbn_shared_ux_file_image.mdx | 2 +- api_docs/kbn_shared_ux_file_image_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_picker.mdx | 2 +- api_docs/kbn_shared_ux_file_types.mdx | 2 +- api_docs/kbn_shared_ux_file_upload.mdx | 2 +- api_docs/kbn_shared_ux_file_util.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_prompt_not_found.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_tabbed_modal.mdx | 2 +- api_docs/kbn_shared_ux_table_persist.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_predicates.mdx | 2 +- api_docs/kbn_sse_utils.mdx | 2 +- api_docs/kbn_sse_utils_client.mdx | 2 +- api_docs/kbn_sse_utils_server.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_synthetics_e2e.mdx | 2 +- api_docs/kbn_synthetics_private_location.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_eui_helpers.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_timerange.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_triggers_actions_ui_types.mdx | 2 +- api_docs/kbn_try_in_console.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_unified_data_table.mdx | 2 +- api_docs/kbn_unified_doc_viewer.mdx | 2 +- api_docs/kbn_unified_field_list.mdx | 2 +- api_docs/kbn_unsaved_changes_badge.mdx | 2 +- api_docs/kbn_unsaved_changes_prompt.mdx | 2 +- api_docs/kbn_use_tracked_promise.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_visualization_ui_components.mdx | 2 +- api_docs/kbn_visualization_utils.mdx | 2 +- api_docs/kbn_xstate_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kbn_zod.mdx | 2 +- api_docs/kbn_zod_helpers.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.devdocs.json | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/links.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/logs_data_access.mdx | 2 +- api_docs/logs_explorer.mdx | 2 +- api_docs/logs_shared.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/metrics_data_access.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/mock_idp_plugin.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/no_data_page.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.devdocs.json | 10 +- api_docs/observability.mdx | 2 +- api_docs/observability_a_i_assistant.mdx | 2 +- api_docs/observability_a_i_assistant_app.mdx | 2 +- .../observability_ai_assistant_management.mdx | 2 +- api_docs/observability_logs_explorer.mdx | 2 +- api_docs/observability_onboarding.mdx | 2 +- api_docs/observability_shared.devdocs.json | 191 ++++++++ api_docs/observability_shared.mdx | 4 +- api_docs/osquery.devdocs.json | 2 +- api_docs/osquery.mdx | 2 +- api_docs/painless_lab.mdx | 2 +- api_docs/plugin_directory.mdx | 39 +- api_docs/presentation_panel.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/profiling_data_access.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.devdocs.json | 14 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/search_assistant.mdx | 2 +- api_docs/search_connectors.mdx | 2 +- api_docs/search_homepage.mdx | 2 +- api_docs/search_indices.devdocs.json | 2 +- api_docs/search_indices.mdx | 2 +- api_docs/search_inference_endpoints.mdx | 2 +- api_docs/search_notebooks.mdx | 2 +- api_docs/search_playground.mdx | 2 +- api_docs/security.devdocs.json | 46 +- api_docs/security.mdx | 4 +- api_docs/security_solution.mdx | 2 +- api_docs/security_solution_ess.mdx | 2 +- api_docs/security_solution_serverless.mdx | 2 +- api_docs/serverless.mdx | 2 +- api_docs/serverless_observability.mdx | 2 +- api_docs/serverless_search.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/slo.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.devdocs.json | 21 + api_docs/triggers_actions_ui.mdx | 4 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_doc_viewer.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/uptime.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 785 files changed, 2829 insertions(+), 1370 deletions(-) create mode 100644 api_docs/kbn_security_authorization_core_common.devdocs.json create mode 100644 api_docs/kbn_security_authorization_core_common.mdx diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 698e60edb913f..14e71f7c63c53 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index e9ca79c99735c..e57887ad6e864 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index 6c9184109b31d..b73f143f818db 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index aeb09c707831d..e0b62e2a37321 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 5cc58b974b805..6b884a953cef4 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index d153cab8edf19..0b57778902f84 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index b9c4fa84ad2b8..89a11b107f41a 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 5e986e354e880..32f4839b9f9ab 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 80a1d827bfb69..9bd5a973d2eb5 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 62c2ce6a22b3a..cbacb005cc0d8 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index ce240016d99cb..7f213dbd3c129 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 1678b5e099623..208a245c0f23c 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 8b7e2f4ffefdf..d685cdf6ff626 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index ee878d9bea765..42f36545759d8 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index f16e4e251c482..f7590e2d6be21 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index a6b9a527bfdb7..4c89f74ec7df0 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index e472e3a2ac10a..df30c16672aeb 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 68e9874c53cf4..dac9e67afcca4 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 8a30fced2c9f1..4fa26365e4b4b 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 1d8967e8624ef..38534f1797418 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 0642555c64e16..794537f7a01cd 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 2ce536d74646d..00966123f1a0e 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index dc25b4e1b1615..9cdcf8758f3f4 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index 08e4ad18227ef..efd3cada05365 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 7fceffbb633e5..941043a023c9d 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index eb2e4f8cdd2b9..5cd056020e220 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_usage.mdx b/api_docs/data_usage.mdx index 5831aaed4c5e7..42a8ada583c69 100644 --- a/api_docs/data_usage.mdx +++ b/api_docs/data_usage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataUsage title: "dataUsage" image: https://source.unsplash.com/400x175/?github description: API docs for the dataUsage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataUsage'] --- import dataUsageObj from './data_usage.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index df5d285afae72..28c9b68491b80 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index ef86aa99559c2..fdf7664f4143a 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index f0cc8a599520d..7000b8d830dac 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 80420e17e0b1c..66465655c6aab 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index d5fd2abe222ae..7fe7e1e8df308 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index 9bce3d34e6973..b9c478858e771 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 3aa09ede1036d..a1679514497b2 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -178,7 +178,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | spaces, security, actions, alerting, aiops, remoteClusters, ml, graph, indexLifecycleManagement, osquery, securitySolution, painlessLab, rollup, searchprofiler, snapshotRestore, transform, upgradeAssistant | 8.8.0 | | | fleet, apm, security, securitySolution | 8.8.0 | | | fleet, apm, security, securitySolution | 8.8.0 | -| | @kbn/security-authorization-core, spaces, security, alerting, cases, @kbn/security-role-management-model | 8.8.0 | +| | spaces, @kbn/security-authorization-core, security, alerting, cases, @kbn/security-role-management-model | 8.8.0 | | | embeddable, presentationUtil, dashboard, lens, discover, graph, links | 8.8.0 | | | security, @kbn/security-role-management-model | 8.8.0 | | | apm | 8.8.0 | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 10c7e8429433e..ea34f5b80a2e5 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -446,7 +446,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 20 more | 8.8.0 | +| | [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 22 more | 8.8.0 | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 2e436b6068df7..78f69bcc14fa1 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -58,7 +58,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ This is relied on by the reporting feature, and should be removed once reporting migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/issues/19914 | -| security | | [app_authorization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.ts#:~:text=getKibanaFeatures), [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getKibanaFeatures), [app_authorization.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.test.ts#:~:text=getKibanaFeatures), [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 28 more | 8.8.0 | +| security | | [app_authorization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.ts#:~:text=getKibanaFeatures), [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getKibanaFeatures), [app_authorization.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.test.ts#:~:text=getKibanaFeatures), [on_post_auth_interceptor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts#:~:text=getKibanaFeatures), [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=getKibanaFeatures), [on_post_auth_interceptor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts#:~:text=getKibanaFeatures), [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 30 more | 8.8.0 | | security | | [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getElasticsearchFeatures), [kibana_privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security/role_management_model/src/__fixtures__/kibana_privileges.ts#:~:text=getElasticsearchFeatures) | 8.8.0 | | security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | | security | | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/plugin.tsx#:~:text=license%24) | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index fbff6ea30cbf4..1b4e2a601d97a 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 5e299afffebcd..daefbbeb4072f 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -1072,7 +1072,7 @@ "section": "def-common.DataView", "text": "DataView" }, - " | undefined>; }" + " | undefined>; updateESQLQuery: (queryOrUpdater: string | ((prevQuery: string) => string)) => void; }" ], "path": "src/plugins/discover/public/application/main/state_management/discover_state.ts", "deprecated": false, diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 9e3a2103c284b..67722014075d7 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index ef4ce3fcac6de..49050557f815d 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index de2e95fefe1af..7959c050b0c37 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 0e7a91fd2fa70..95b4ecce07bdf 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index a93f3d77c50bb..fd44f4af7ea48 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index af504fe0c60dc..138cd07b793b6 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 1878e0cd89b66..0897124234be5 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 4ef53e361fcf1..2c97d81214659 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 78ae48a4ef7ef..a833c125f0fad 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/entities_data_access.mdx b/api_docs/entities_data_access.mdx index 28b7d13d04ca1..b096b41e56556 100644 --- a/api_docs/entities_data_access.mdx +++ b/api_docs/entities_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entitiesDataAccess title: "entitiesDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the entitiesDataAccess plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entitiesDataAccess'] --- import entitiesDataAccessObj from './entities_data_access.devdocs.json'; diff --git a/api_docs/entity_manager.mdx b/api_docs/entity_manager.mdx index b82a89e5537f6..a6ad732ff5bae 100644 --- a/api_docs/entity_manager.mdx +++ b/api_docs/entity_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entityManager title: "entityManager" image: https://source.unsplash.com/400x175/?github description: API docs for the entityManager plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entityManager'] --- import entityManagerObj from './entity_manager.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 32e3f0cc11ffd..618982230c4f9 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql.mdx b/api_docs/esql.mdx index d8a9f83919814..7330c67b76eda 100644 --- a/api_docs/esql.mdx +++ b/api_docs/esql.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esql title: "esql" image: https://source.unsplash.com/400x175/?github description: API docs for the esql plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esql'] --- import esqlObj from './esql.devdocs.json'; diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index 3849cfc4289ec..f402f9f3fbbfd 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 91bcd2a82f34f..c1163ff27b4f9 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index 357bdb7a2714b..58d551c473d96 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index dbac9da4ae08e..851e0d7ffe5bb 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index af8d2a0491344..5ce81f3184fd2 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index b871cefac84b4..eb81547f00375 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 65e7031aeaade..dee6ff22e02bd 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 7f2078f3a32c4..e27447e375297 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 2a5e0a4784429..273194c55a127 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index fa93ca568eedc..2b0fe3d4f5859 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 097e896f026f9..f9e8795dc19b3 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index f68e67a9375f5..450c218fffe2a 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index dc617e067039d..fdf1491423803 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 96e1c23478ed5..ee64700ccfc46 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 368b15186f10d..74ed7512b07a0 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index adaac6483e7cd..75107fa8deaea 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index ff76dc878746b..e2d5305ebe7d2 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 19278feebba74..46b38d730fa82 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 5d0168ecb80aa..b8015be23e404 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.devdocs.json b/api_docs/features.devdocs.json index 4a3b4f0fcc0eb..ffb0475bb6b5f 100644 --- a/api_docs/features.devdocs.json +++ b/api_docs/features.devdocs.json @@ -56,7 +56,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ id: string; name: string; description?: string | undefined; category: Readonly<{ id: string; label: string; ariaLabel?: string | undefined; order?: number | undefined; euiIconType?: string | undefined; }>; order?: number | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; privileges: Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ id: string; name: string; description?: string | undefined; category: Readonly<{ id: string; label: string; ariaLabel?: string | undefined; order?: number | undefined; euiIconType?: string | undefined; }>; order?: number | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; privileges: Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -64,7 +64,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }>[]; }> | undefined; hidden?: boolean | undefined; scope?: readonly ", + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }>[]; }> | undefined; hidden?: boolean | undefined; scope?: readonly ", { "pluginId": "features", "scope": "common", @@ -72,7 +72,7 @@ "section": "def-common.KibanaFeatureScope", "text": "KibanaFeatureScope" }, - "[] | undefined; }>" + "[] | undefined; readonly deprecated?: Readonly<{ readonly notice: string; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -93,6 +93,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "features", + "id": "def-public.KibanaFeature.deprecated", + "type": "Object", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "Readonly<{ readonly notice: string; }> | undefined" + ], + "path": "x-pack/plugins/features/common/kibana_feature.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "features", "id": "def-public.KibanaFeature.hidden", @@ -224,7 +238,7 @@ "label": "privileges", "description": [], "signature": [ - "Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }> | null" + "Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }> | null" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -277,7 +291,7 @@ "label": "reserved", "description": [], "signature": [ - "Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }>[]; }> | undefined" + "Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }>[]; }> | undefined" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -548,6 +562,46 @@ "path": "x-pack/plugins/features/common/feature_kibana_privileges.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-public.FeatureKibanaPrivileges.replacedBy", + "type": "CompoundType", + "tags": [], + "label": "replacedBy", + "description": [ + "\nAn optional list of other registered feature or sub-feature privileges that, when combined, grant equivalent access\nif the feature this privilege belongs to becomes deprecated. The extended definition allows separate lists of\nprivileges to be defined for the default and minimal (excludes any automatically granted sub-feature privileges)\nsets. This property can only be set if the feature is marked as deprecated." + ], + "signature": [ + "readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[] | { default: readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[]; minimal: readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[]; } | undefined" + ], + "path": "x-pack/plugins/features/common/feature_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -890,6 +944,22 @@ "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-public.KibanaFeatureConfig.deprecated", + "type": "Object", + "tags": [], + "label": "deprecated", + "description": [ + "\nIf defined, the feature is considered deprecated and won't be available to users when configuring roles or Spaces." + ], + "signature": [ + "Readonly<{ notice: string; }> | undefined" + ], + "path": "x-pack/plugins/features/common/kibana_feature.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1020,7 +1090,7 @@ "section": "def-common.FeatureKibanaPrivileges", "text": "FeatureKibanaPrivileges" }, - ", \"excludeFromBasePrivileges\" | \"composedOf\">" + ", \"excludeFromBasePrivileges\" | \"composedOf\" | \"replacedBy\">" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -1083,6 +1153,30 @@ "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-public.SubFeaturePrivilegeConfig.replacedBy", + "type": "Object", + "tags": [], + "label": "replacedBy", + "description": [ + "\nAn optional list of other registered feature or sub-feature privileges that, when combined, grant equivalent access\nif the feature this sub-feature privilege belongs to becomes deprecated. This property can only be set if the\nfeature is marked as deprecated." + ], + "signature": [ + "readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[] | undefined" + ], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1387,7 +1481,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ id: string; name: string; description?: string | undefined; category: Readonly<{ id: string; label: string; ariaLabel?: string | undefined; order?: number | undefined; euiIconType?: string | undefined; }>; order?: number | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; privileges: Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ id: string; name: string; description?: string | undefined; category: Readonly<{ id: string; label: string; ariaLabel?: string | undefined; order?: number | undefined; euiIconType?: string | undefined; }>; order?: number | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; privileges: Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -1395,7 +1489,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }>[]; }> | undefined; hidden?: boolean | undefined; scope?: readonly ", + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }>[]; }> | undefined; hidden?: boolean | undefined; scope?: readonly ", { "pluginId": "features", "scope": "common", @@ -1403,7 +1497,7 @@ "section": "def-common.KibanaFeatureScope", "text": "KibanaFeatureScope" }, - "[] | undefined; }>" + "[] | undefined; readonly deprecated?: Readonly<{ readonly notice: string; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -1424,6 +1518,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "features", + "id": "def-server.KibanaFeature.deprecated", + "type": "Object", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "Readonly<{ readonly notice: string; }> | undefined" + ], + "path": "x-pack/plugins/features/common/kibana_feature.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "features", "id": "def-server.KibanaFeature.hidden", @@ -1555,7 +1663,7 @@ "label": "privileges", "description": [], "signature": [ - "Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }> | null" + "Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }> | null" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -1608,7 +1716,7 @@ "label": "reserved", "description": [], "signature": [ - "Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }>[]; }> | undefined" + "Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }>[]; }> | undefined" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -2058,6 +2166,46 @@ "path": "x-pack/plugins/features/common/feature_kibana_privileges.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-server.FeatureKibanaPrivileges.replacedBy", + "type": "CompoundType", + "tags": [], + "label": "replacedBy", + "description": [ + "\nAn optional list of other registered feature or sub-feature privileges that, when combined, grant equivalent access\nif the feature this privilege belongs to becomes deprecated. The extended definition allows separate lists of\nprivileges to be defined for the default and minimal (excludes any automatically granted sub-feature privileges)\nsets. This property can only be set if the feature is marked as deprecated." + ], + "signature": [ + "readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[] | { default: readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[]; minimal: readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[]; } | undefined" + ], + "path": "x-pack/plugins/features/common/feature_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2194,10 +2342,6 @@ "removeBy": "8.8.0", "trackAdoption": false, "references": [ - { - "plugin": "@kbn/security-authorization-core", - "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.ts" - }, { "plugin": "spaces", "path": "x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts" @@ -2206,6 +2350,10 @@ "plugin": "spaces", "path": "x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts" }, + { + "plugin": "@kbn/security-authorization-core", + "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/authorization/app_authorization.ts" @@ -2350,6 +2498,14 @@ "plugin": "@kbn/security-authorization-core", "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts" }, + { + "plugin": "@kbn/security-authorization-core", + "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts" + }, + { + "plugin": "@kbn/security-authorization-core", + "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts" + }, { "plugin": "@kbn/security-authorization-core", "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts" @@ -2975,13 +3131,119 @@ "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-server.KibanaFeatureConfig.deprecated", + "type": "Object", + "tags": [], + "label": "deprecated", + "description": [ + "\nIf defined, the feature is considered deprecated and won't be available to users when configuring roles or Spaces." + ], + "signature": [ + "Readonly<{ notice: string; }> | undefined" + ], + "path": "x-pack/plugins/features/common/kibana_feature.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false } ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "features", + "id": "def-server.SubFeaturePrivilegeIterator", + "type": "Type", + "tags": [], + "label": "SubFeaturePrivilegeIterator", + "description": [ + "\nUtility for iterating through all sub-feature privileges belonging to a specific feature.\n" + ], + "signature": [ + "(feature: ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.KibanaFeature", + "text": "KibanaFeature" + }, + ", licenseHasAtLeast: (licenseType: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\") => boolean | undefined) => IterableIterator<", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.SubFeaturePrivilegeConfig", + "text": "SubFeaturePrivilegeConfig" + }, + ">" + ], + "path": "x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "features", + "id": "def-server.SubFeaturePrivilegeIterator.$1", + "type": "Object", + "tags": [], + "label": "feature", + "description": [ + "the feature whose sub-feature privileges to iterate through." + ], + "signature": [ + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.KibanaFeature", + "text": "KibanaFeature" + } + ], + "path": "x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-server.SubFeaturePrivilegeIterator.$2", + "type": "Function", + "tags": [], + "label": "licenseHasAtLeast", + "description": [], + "signature": [ + "(licenseType: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\") => boolean | undefined" + ], + "path": "x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "features", + "id": "def-server.SubFeaturePrivilegeIterator.$2.$1", + "type": "CompoundType", + "tags": [], + "label": "licenseType", + "description": [], + "signature": [ + "\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\"" + ], + "path": "x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + } + ], "objects": [ { "parentPluginId": "features", @@ -3240,7 +3502,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ id: string; name: string; description?: string | undefined; category: Readonly<{ id: string; label: string; ariaLabel?: string | undefined; order?: number | undefined; euiIconType?: string | undefined; }>; order?: number | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; privileges: Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ id: string; name: string; description?: string | undefined; category: Readonly<{ id: string; label: string; ariaLabel?: string | undefined; order?: number | undefined; euiIconType?: string | undefined; }>; order?: number | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; privileges: Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -3248,7 +3510,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }>[]; }> | undefined; hidden?: boolean | undefined; scope?: readonly ", + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }>[]; }> | undefined; hidden?: boolean | undefined; scope?: readonly ", { "pluginId": "features", "scope": "common", @@ -3256,7 +3518,7 @@ "section": "def-common.KibanaFeatureScope", "text": "KibanaFeatureScope" }, - "[] | undefined; }>" + "[] | undefined; readonly deprecated?: Readonly<{ readonly notice: string; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -3277,6 +3539,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "features", + "id": "def-common.KibanaFeature.deprecated", + "type": "Object", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "Readonly<{ readonly notice: string; }> | undefined" + ], + "path": "x-pack/plugins/features/common/kibana_feature.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "features", "id": "def-common.KibanaFeature.hidden", @@ -3408,7 +3684,7 @@ "label": "privileges", "description": [], "signature": [ - "Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }> | null" + "Readonly<{ all: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; read: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }> | null" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -3461,7 +3737,7 @@ "label": "reserved", "description": [], "signature": [ - "Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; }>; }>[]; }> | undefined" + "Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; composedOf?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | Readonly<{ default: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; minimal: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[]; }> | undefined; }>; }>[]; }> | undefined" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -3556,7 +3832,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }>" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -3593,7 +3869,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -3637,7 +3913,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"read\" | \"all\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; replacedBy?: readonly Readonly<{ feature: string; privileges: readonly string[]; }>[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; settings?: readonly string[] | undefined; }> | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; app?: readonly string[] | undefined; ui: readonly string[]; catalogue?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; description?: string | undefined; }" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -4044,6 +4320,46 @@ "path": "x-pack/plugins/features/common/feature_kibana_privileges.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-common.FeatureKibanaPrivileges.replacedBy", + "type": "CompoundType", + "tags": [], + "label": "replacedBy", + "description": [ + "\nAn optional list of other registered feature or sub-feature privileges that, when combined, grant equivalent access\nif the feature this privilege belongs to becomes deprecated. The extended definition allows separate lists of\nprivileges to be defined for the default and minimal (excludes any automatically granted sub-feature privileges)\nsets. This property can only be set if the feature is marked as deprecated." + ], + "signature": [ + "readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[] | { default: readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[]; minimal: readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[]; } | undefined" + ], + "path": "x-pack/plugins/features/common/feature_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -4431,6 +4747,22 @@ "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-common.KibanaFeatureConfig.deprecated", + "type": "Object", + "tags": [], + "label": "deprecated", + "description": [ + "\nIf defined, the feature is considered deprecated and won't be available to users when configuring roles or Spaces." + ], + "signature": [ + "Readonly<{ notice: string; }> | undefined" + ], + "path": "x-pack/plugins/features/common/kibana_feature.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -4561,7 +4893,7 @@ "section": "def-common.FeatureKibanaPrivileges", "text": "FeatureKibanaPrivileges" }, - ", \"excludeFromBasePrivileges\" | \"composedOf\">" + ", \"excludeFromBasePrivileges\" | \"composedOf\" | \"replacedBy\">" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -4624,6 +4956,30 @@ "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-common.SubFeaturePrivilegeConfig.replacedBy", + "type": "Object", + "tags": [], + "label": "replacedBy", + "description": [ + "\nAn optional list of other registered feature or sub-feature privileges that, when combined, grant equivalent access\nif the feature this sub-feature privilege belongs to becomes deprecated. This property can only be set if the\nfeature is marked as deprecated." + ], + "signature": [ + "readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[] | undefined" + ], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 9465529cbe29d..d0c2bb2f65960 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 255 | 0 | 105 | 2 | +| 270 | 0 | 110 | 2 | ## Client @@ -51,6 +51,9 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core ### Interfaces +### Consts, variables and types + + ## Common ### Classes diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 7268f3f9bc2dc..a10cee2570f2d 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.devdocs.json b/api_docs/fields_metadata.devdocs.json index cf176b0b6c99b..a4807cac5bbd4 100644 --- a/api_docs/fields_metadata.devdocs.json +++ b/api_docs/fields_metadata.devdocs.json @@ -277,6 +277,43 @@ ] } ] + }, + { + "parentPluginId": "fieldsMetadata", + "id": "def-server.FieldsMetadataServerSetup.registerIntegrationListExtractor", + "type": "Function", + "tags": [], + "label": "registerIntegrationListExtractor", + "description": [], + "signature": [ + "(extractor: ", + "IntegrationListExtractor", + ") => void" + ], + "path": "x-pack/plugins/fields_metadata/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "fieldsMetadata", + "id": "def-server.FieldsMetadataServerSetup.registerIntegrationListExtractor.$1", + "type": "Function", + "tags": [], + "label": "extractor", + "description": [], + "signature": [ + "() => Promise<", + "ExtractedIntegration", + "[]>" + ], + "path": "x-pack/plugins/fields_metadata/server/services/fields_metadata/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ] } ], "lifecycle": "setup", diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index 3acca9f43f118..5417aa3ba8312 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 42 | 0 | 42 | 7 | +| 44 | 0 | 44 | 9 | ## Client diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 306eb9c39fc76..f0dafa13c2503 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 1a932999dc80b..961f3036123cd 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index b84c324e00242..070fb65b1c6fa 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 53c5cfd820a7c..49038d83a1741 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 639df0a49c9eb..0563cfdcfe583 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 06ade039f3eb8..89d3809cba3a0 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 2e59d5e7c6b3b..6bd303b4cf689 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index d3838aeaa9c11..c07ea91aef55e 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index ae3cd7cee99a0..09affdb5de4f5 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index ae645465e9149..d74d4b0d573f3 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/inference.mdx b/api_docs/inference.mdx index 9bdfca2841cee..a1c5f753b6abb 100644 --- a/api_docs/inference.mdx +++ b/api_docs/inference.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inference title: "inference" image: https://source.unsplash.com/400x175/?github description: API docs for the inference plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inference'] --- import inferenceObj from './inference.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index f4703c03a904a..503174bdfc895 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index 5f3e4cf5b5b67..997f0812ceee5 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 917d870704190..78668cea65bd8 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.devdocs.json b/api_docs/integration_assistant.devdocs.json index 49112b735fcce..e13157f3da38c 100644 --- a/api_docs/integration_assistant.devdocs.json +++ b/api_docs/integration_assistant.devdocs.json @@ -216,7 +216,7 @@ "label": "AnalyzeLogsRequestBody", "description": [], "signature": [ - "{ connectorId: string; packageName: string; dataStreamName: string; logSamples: string[]; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }" + "{ connectorId: string; packageName: string; dataStreamName: string; packageTitle: string; dataStreamTitle: string; logSamples: string[]; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }" ], "path": "x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.gen.ts", "deprecated": false, @@ -231,7 +231,7 @@ "label": "AnalyzeLogsResponse", "description": [], "signature": [ - "{ results: { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }; additionalProcessors?: ", + "{ results: { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }; additionalProcessors?: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -270,7 +270,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }; }" + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }; }" ], "path": "x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.gen.ts", "deprecated": false, @@ -297,7 +297,7 @@ "label": "CategorizationRequestBody", "description": [], "signature": [ - "{ connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", + "{ connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -494,7 +494,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }" + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }" ], "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts", "deprecated": false, @@ -538,7 +538,7 @@ "label": "EcsMappingRequestBody", "description": [], "signature": [ - "{ connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; mapping?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; additionalProcessors?: ", + "{ connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; mapping?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; additionalProcessors?: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -627,7 +627,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }" + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }" ], "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts", "deprecated": false, @@ -761,7 +761,7 @@ "label": "RelatedRequestBody", "description": [], "signature": [ - "{ connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", + "{ connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -825,7 +825,7 @@ "\nFormat of the provided log samples." ], "signature": [ - "{ name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }" + "{ name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }" ], "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts", "deprecated": false, @@ -859,7 +859,7 @@ "label": "AnalyzeLogsRequestBody", "description": [], "signature": [ - "Zod.ZodObject<{ packageName: Zod.ZodString; dataStreamName: Zod.ZodString; logSamples: Zod.ZodArray; connectorId: Zod.ZodString; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; dataStreamName: string; logSamples: string[]; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }, { connectorId: string; packageName: string; dataStreamName: string; logSamples: string[]; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }>" + "Zod.ZodObject<{ packageName: Zod.ZodString; dataStreamName: Zod.ZodString; packageTitle: Zod.ZodString; dataStreamTitle: Zod.ZodString; logSamples: Zod.ZodArray; connectorId: Zod.ZodString; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; dataStreamName: string; packageTitle: string; dataStreamTitle: string; logSamples: string[]; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }, { connectorId: string; packageName: string; dataStreamName: string; packageTitle: string; dataStreamTitle: string; logSamples: string[]; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }>" ], "path": "x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.gen.ts", "deprecated": false, @@ -884,7 +884,7 @@ }, ", Zod.ZodTypeDef, ", "ESProcessorItemInput", - ">, \"many\">>; results: Zod.ZodObject<{ samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; parsedSamples: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }, { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }>; }, \"strip\", Zod.ZodTypeAny, { results: { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }; additionalProcessors?: ", + ">, \"many\">>; results: Zod.ZodObject<{ samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; parsedSamples: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }, { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }>; }, \"strip\", Zod.ZodTypeAny, { results: { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }; additionalProcessors?: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -892,7 +892,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }, { results: { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }; additionalProcessors?: ", + "[] | undefined; }, { results: { samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; parsedSamples: string[]; }; additionalProcessors?: ", "ESProcessorItemInput", "[] | undefined; }>" ], @@ -949,7 +949,7 @@ "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }>; docs: Zod.ZodArray, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>, \"many\">; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; celInput: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>; redactVars: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }>>; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }>; docs: Zod.ZodArray, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>, \"many\">; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; celInput: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>; redactVars: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }>>; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -965,11 +965,11 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }>, \"many\">; logo: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }>, \"many\">; logo: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -985,11 +985,11 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1005,11 +1005,11 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }; }, { integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }; }, { integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }; }>" + "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }; }>" ], "path": "x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.gen.ts", "deprecated": false, @@ -1064,7 +1064,7 @@ "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }>; connectorId: Zod.ZodString; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", + "[] | undefined; }>; connectorId: Zod.ZodString; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1080,7 +1080,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", + "[] | undefined; }; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", @@ -1369,7 +1369,7 @@ "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }>; docs: Zod.ZodArray, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>, \"many\">; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; celInput: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>; redactVars: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }>>; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }>; docs: Zod.ZodArray, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>, \"many\">; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; celInput: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>; redactVars: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }>>; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1385,11 +1385,11 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }>" + "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }>" ], "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts", "deprecated": false, @@ -1419,7 +1419,7 @@ "label": "EcsMappingRequestBody", "description": [], "signature": [ - "Zod.ZodObject<{ packageName: Zod.ZodString; dataStreamName: Zod.ZodString; rawSamples: Zod.ZodArray; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; mapping: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; additionalProcessors: Zod.ZodOptional; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; mapping: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; additionalProcessors: Zod.ZodOptional, \"many\">>; connectorId: Zod.ZodString; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; mapping?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; additionalProcessors?: ", + ">, \"many\">>; connectorId: Zod.ZodString; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; mapping?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; additionalProcessors?: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1437,7 +1437,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; mapping?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; additionalProcessors?: ", + "[] | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; mapping?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; additionalProcessors?: ", "ESProcessorItemInput", "[] | undefined; }>" ], @@ -1629,7 +1629,7 @@ "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }>; docs: Zod.ZodArray, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>, \"many\">; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; celInput: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>; redactVars: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }>>; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }>; docs: Zod.ZodArray, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>, \"many\">; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; celInput: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>; redactVars: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }, { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; }>>; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1645,11 +1645,11 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }>, \"many\">; logo: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }>, \"many\">; logo: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1665,11 +1665,11 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + "[] | undefined; }; docs: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws-cloudwatch\" | \"aws-s3\" | \"azure-blob-storage\" | \"azure-eventhub\" | \"cel\" | \"cloudfoundry\" | \"filestream\" | \"gcp-pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }>" + "[] | undefined; }; docs: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; celInput?: { program: string; stateSettings: {} & { [k: string]: unknown; }; redactVars: string[]; } | undefined; }[]; logo?: string | undefined; }>" ], "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts", "deprecated": false, @@ -1794,7 +1794,7 @@ "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", - "[] | undefined; }>; connectorId: Zod.ZodString; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", + "[] | undefined; }>; connectorId: Zod.ZodString; samplesFormat: Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>; langSmithOptions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", { "pluginId": "integrationAssistant", "scope": "common", @@ -1810,7 +1810,7 @@ "section": "def-common.ESProcessorItem", "text": "ESProcessorItem" }, - "[] | undefined; }; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", + "[] | undefined; }; langSmithOptions?: { apiKey: string; projectName: string; } | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; samplesFormat: { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }; dataStreamName: string; currentPipeline: { processors: ", "ESProcessorItemInput", "[]; version?: number | undefined; name?: string | undefined; description?: string | undefined; on_failure?: ", "ESProcessorItemInput", @@ -1924,7 +1924,7 @@ "label": "SamplesFormat", "description": [], "signature": [ - "Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; multiline?: boolean | undefined; json_path?: string[] | undefined; }>" + "Zod.ZodObject<{ name: Zod.ZodEnum<[\"ndjson\", \"json\", \"csv\", \"structured\", \"unstructured\", \"unsupported\"]>; multiline: Zod.ZodOptional; header: Zod.ZodOptional; columns: Zod.ZodOptional>; json_path: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }, { name: \"unsupported\" | \"json\" | \"ndjson\" | \"csv\" | \"structured\" | \"unstructured\"; columns?: string[] | undefined; header?: boolean | undefined; multiline?: boolean | undefined; json_path?: string[] | undefined; }>" ], "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts", "deprecated": false, diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index f2dcd0a62e9a2..b8ccb43bb1162 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/integrationAssistant title: "integrationAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the integrationAssistant plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] --- import integrationAssistantObj from './integration_assistant.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index cd463cb84b99e..d61969fb7cc13 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/inventory.mdx b/api_docs/inventory.mdx index 5c0cdddb7f251..fa29659970e63 100644 --- a/api_docs/inventory.mdx +++ b/api_docs/inventory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inventory title: "inventory" image: https://source.unsplash.com/400x175/?github description: API docs for the inventory plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inventory'] --- import inventoryObj from './inventory.devdocs.json'; diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index 874df9987f7be..a2172d6a78b20 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; diff --git a/api_docs/investigate_app.mdx b/api_docs/investigate_app.mdx index 89d3916fe7dcc..33b2bf78c49fe 100644 --- a/api_docs/investigate_app.mdx +++ b/api_docs/investigate_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigateApp title: "investigateApp" image: https://source.unsplash.com/400x175/?github description: API docs for the investigateApp plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigateApp'] --- import investigateAppObj from './investigate_app.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index 869e749461c80..5bbd6049dc7dc 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_ai_assistant.mdx b/api_docs/kbn_ai_assistant.mdx index ce43df4cc4bbc..eccbdc878e8fd 100644 --- a/api_docs/kbn_ai_assistant.mdx +++ b/api_docs/kbn_ai_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ai-assistant title: "@kbn/ai-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ai-assistant plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ai-assistant'] --- import kbnAiAssistantObj from './kbn_ai_assistant.devdocs.json'; diff --git a/api_docs/kbn_ai_assistant_common.mdx b/api_docs/kbn_ai_assistant_common.mdx index 47ec9af07d960..fbc39d41e3c54 100644 --- a/api_docs/kbn_ai_assistant_common.mdx +++ b/api_docs/kbn_ai_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ai-assistant-common title: "@kbn/ai-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ai-assistant-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ai-assistant-common'] --- import kbnAiAssistantCommonObj from './kbn_ai_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index e3b4a8f20185b..139502c3b0040 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 6ef56e0d141f8..5ad3d99fb92e8 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index 3433d8306c13d..26167e7d2b71a 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index f120de86593fa..188cb6b194b77 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index 26bbfc526868d..14500606b1c20 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index e2de558752922..17b8817590746 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index edcf8ff7f8596..230900d242544 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.devdocs.json b/api_docs/kbn_alerts_as_data_utils.devdocs.json index 127fb00d4c2ac..b26349824b605 100644 --- a/api_docs/kbn_alerts_as_data_utils.devdocs.json +++ b/api_docs/kbn_alerts_as_data_utils.devdocs.json @@ -196,7 +196,7 @@ "label": "AADAlert", "description": [], "signature": [ - "({ '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & {} & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & { 'agent.name'?: string | undefined; 'container.id'?: string | undefined; 'error.grouping_key'?: string | undefined; 'error.grouping_name'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'processor.event'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.name'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; 'slo.id'?: string | undefined; 'slo.instanceId'?: string | undefined; 'slo.revision'?: string | number | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'agent.name'?: string | undefined; 'anomaly.bucket_span.minutes'?: string | undefined; 'anomaly.start'?: string | number | undefined; configId?: string | undefined; 'error.message'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'location.id'?: string[] | undefined; 'location.name'?: string[] | undefined; 'monitor.id'?: string | undefined; 'monitor.name'?: string | undefined; 'monitor.state.id'?: string | undefined; 'monitor.tags'?: string[] | undefined; 'monitor.type'?: string | undefined; 'observer.geo.name'?: string[] | undefined; 'observer.name'?: string[] | undefined; 'service.name'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.x509.issuer.common_name'?: string | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.subject.common_name'?: string | undefined; 'url.full'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({ '@timestamp': string | number; 'kibana.alert.ancestors': { depth: string | number; id: string; index: string; type: string; }[]; 'kibana.alert.depth': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.original_event.action': string; 'kibana.alert.original_event.category': string[]; 'kibana.alert.original_event.created': string | number; 'kibana.alert.original_event.dataset': string; 'kibana.alert.original_event.id': string; 'kibana.alert.original_event.ingested': string | number; 'kibana.alert.original_event.kind': string; 'kibana.alert.original_event.module': string; 'kibana.alert.original_event.original': string; 'kibana.alert.original_event.outcome': string; 'kibana.alert.original_event.provider': string; 'kibana.alert.original_event.sequence': string | number; 'kibana.alert.original_event.type': string[]; 'kibana.alert.original_time': string | number; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.false_positives': string[]; 'kibana.alert.rule.max_signals': (string | number)[]; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.threat.framework': string; 'kibana.alert.rule.threat.tactic.id': string; 'kibana.alert.rule.threat.tactic.name': string; 'kibana.alert.rule.threat.tactic.reference': string; 'kibana.alert.rule.threat.technique.id': string; 'kibana.alert.rule.threat.technique.name': string; 'kibana.alert.rule.threat.technique.reference': string; 'kibana.alert.rule.threat.technique.subtechnique.id': string; 'kibana.alert.rule.threat.technique.subtechnique.name': string; 'kibana.alert.rule.threat.technique.subtechnique.reference': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'ecs.version'?: string | undefined; 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'host.asset.criticality'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.ancestors.rule'?: string | undefined; 'kibana.alert.building_block_type'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.group.id'?: string | undefined; 'kibana.alert.group.index'?: number | undefined; 'kibana.alert.host.criticality_level'?: string | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.new_terms'?: string[] | undefined; 'kibana.alert.original_event.agent_id_status'?: string | undefined; 'kibana.alert.original_event.code'?: string | undefined; 'kibana.alert.original_event.duration'?: string | undefined; 'kibana.alert.original_event.end'?: string | number | undefined; 'kibana.alert.original_event.hash'?: string | undefined; 'kibana.alert.original_event.reason'?: string | undefined; 'kibana.alert.original_event.reference'?: string | undefined; 'kibana.alert.original_event.risk_score'?: number | undefined; 'kibana.alert.original_event.risk_score_norm'?: number | undefined; 'kibana.alert.original_event.severity'?: string | number | undefined; 'kibana.alert.original_event.start'?: string | number | undefined; 'kibana.alert.original_event.timezone'?: string | undefined; 'kibana.alert.original_event.url'?: string | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.building_block_type'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.immutable'?: string[] | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.rule.timeline_id'?: string[] | undefined; 'kibana.alert.rule.timeline_title'?: string[] | undefined; 'kibana.alert.rule.timestamp_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.threshold_result.cardinality'?: unknown; 'kibana.alert.threshold_result.count'?: string | number | undefined; 'kibana.alert.threshold_result.from'?: string | number | undefined; 'kibana.alert.threshold_result.terms'?: { field?: string | undefined; value?: string | undefined; }[] | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.user.criticality_level'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.alert.workflow_user'?: string | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; 'user.asset.criticality'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({ 'kibana.alert.job_id': string; } & { 'kibana.alert.anomaly_score'?: number[] | undefined; 'kibana.alert.anomaly_timestamp'?: string | number | undefined; 'kibana.alert.is_interim'?: boolean | undefined; 'kibana.alert.top_influencers'?: { influencer_field_name?: string | undefined; influencer_field_value?: string | undefined; influencer_score?: number | undefined; initial_influencer_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; timestamp?: string | number | undefined; }[] | undefined; 'kibana.alert.top_records'?: { actual?: number | undefined; by_field_name?: string | undefined; by_field_value?: string | undefined; detector_index?: number | undefined; field_name?: string | undefined; function?: string | undefined; initial_record_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; over_field_name?: string | undefined; over_field_value?: string | undefined; partition_field_name?: string | undefined; partition_field_value?: string | undefined; record_score?: number | undefined; timestamp?: string | number | undefined; typical?: number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & { 'kibana.alert.datafeed_results'?: { datafeed_id?: string | undefined; datafeed_state?: string | undefined; job_id?: string | undefined; job_state?: string | undefined; }[] | undefined; 'kibana.alert.delayed_data_results'?: { annotation?: string | undefined; end_timestamp?: string | number | undefined; job_id?: string | undefined; missed_docs_count?: string | number | undefined; }[] | undefined; 'kibana.alert.job_errors_results'?: { errors?: unknown; job_id?: string | undefined; }[] | undefined; 'kibana.alert.mml_results'?: { job_id?: string | undefined; log_time?: string | number | undefined; memory_status?: string | undefined; model_bytes?: string | number | undefined; model_bytes_exceeded?: string | number | undefined; model_bytes_memory_limit?: string | number | undefined; peak_model_bytes?: string | number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & { 'kibana.alert.results'?: { description?: string | undefined; health_status?: string | undefined; issues?: unknown; node_name?: string | undefined; transform_id?: string | undefined; transform_state?: string | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; })" + "({ '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & {} & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & { 'agent.name'?: string | undefined; 'container.id'?: string | undefined; 'error.grouping_key'?: string | undefined; 'error.grouping_name'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'processor.event'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.name'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; 'slo.id'?: string | undefined; 'slo.instanceId'?: string | undefined; 'slo.revision'?: string | number | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({} & { 'agent.name'?: string | undefined; 'anomaly.bucket_span.minutes'?: string | undefined; 'anomaly.start'?: string | number | undefined; configId?: string | undefined; 'error.message'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'location.id'?: string[] | undefined; 'location.name'?: string[] | undefined; 'monitor.id'?: string | undefined; 'monitor.name'?: string | undefined; 'monitor.state.id'?: string | undefined; 'monitor.tags'?: string[] | undefined; 'monitor.type'?: string | undefined; 'observer.geo.name'?: string[] | undefined; 'observer.name'?: string[] | undefined; 'service.name'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.x509.issuer.common_name'?: string | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.subject.common_name'?: string | undefined; 'url.full'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({ '@timestamp': string | number; 'kibana.alert.ancestors': { depth: string | number; id: string; index: string; type: string; }[]; 'kibana.alert.depth': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.original_event.action': string; 'kibana.alert.original_event.category': string[]; 'kibana.alert.original_event.created': string | number; 'kibana.alert.original_event.dataset': string; 'kibana.alert.original_event.id': string; 'kibana.alert.original_event.ingested': string | number; 'kibana.alert.original_event.kind': string; 'kibana.alert.original_event.module': string; 'kibana.alert.original_event.original': string; 'kibana.alert.original_event.outcome': string; 'kibana.alert.original_event.provider': string; 'kibana.alert.original_event.sequence': string | number; 'kibana.alert.original_event.type': string[]; 'kibana.alert.original_time': string | number; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.false_positives': string[]; 'kibana.alert.rule.max_signals': (string | number)[]; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.threat.framework': string; 'kibana.alert.rule.threat.tactic.id': string; 'kibana.alert.rule.threat.tactic.name': string; 'kibana.alert.rule.threat.tactic.reference': string; 'kibana.alert.rule.threat.technique.id': string; 'kibana.alert.rule.threat.technique.name': string; 'kibana.alert.rule.threat.technique.reference': string; 'kibana.alert.rule.threat.technique.subtechnique.id': string; 'kibana.alert.rule.threat.technique.subtechnique.name': string; 'kibana.alert.rule.threat.technique.subtechnique.reference': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'ecs.version'?: string | undefined; 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'host.asset.criticality'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.ancestors.rule'?: string | undefined; 'kibana.alert.building_block_type'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.group.id'?: string | undefined; 'kibana.alert.group.index'?: number | undefined; 'kibana.alert.host.criticality_level'?: string | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.new_terms'?: string[] | undefined; 'kibana.alert.original_event.agent_id_status'?: string | undefined; 'kibana.alert.original_event.code'?: string | undefined; 'kibana.alert.original_event.duration'?: string | undefined; 'kibana.alert.original_event.end'?: string | number | undefined; 'kibana.alert.original_event.hash'?: string | undefined; 'kibana.alert.original_event.reason'?: string | undefined; 'kibana.alert.original_event.reference'?: string | undefined; 'kibana.alert.original_event.risk_score'?: number | undefined; 'kibana.alert.original_event.risk_score_norm'?: number | undefined; 'kibana.alert.original_event.severity'?: string | number | undefined; 'kibana.alert.original_event.start'?: string | number | undefined; 'kibana.alert.original_event.timezone'?: string | undefined; 'kibana.alert.original_event.url'?: string | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.building_block_type'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.immutable'?: string[] | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.rule.timeline_id'?: string[] | undefined; 'kibana.alert.rule.timeline_title'?: string[] | undefined; 'kibana.alert.rule.timestamp_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.threshold_result.cardinality'?: unknown; 'kibana.alert.threshold_result.count'?: string | number | undefined; 'kibana.alert.threshold_result.from'?: string | number | undefined; 'kibana.alert.threshold_result.terms'?: { field?: string | undefined; value?: string | undefined; }[] | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.user.criticality_level'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.alert.workflow_user'?: string | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; 'user.asset.criticality'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }) | ({ 'kibana.alert.job_id': string; } & { 'kibana.alert.anomaly_score'?: number[] | undefined; 'kibana.alert.anomaly_timestamp'?: string | number | undefined; 'kibana.alert.is_interim'?: boolean | undefined; 'kibana.alert.top_influencers'?: { influencer_field_name?: string | undefined; influencer_field_value?: string | undefined; influencer_score?: number | undefined; initial_influencer_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; timestamp?: string | number | undefined; }[] | undefined; 'kibana.alert.top_records'?: { actual?: number | undefined; by_field_name?: string | undefined; by_field_value?: string | undefined; detector_index?: number | undefined; field_name?: string | undefined; function?: string | undefined; initial_record_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; over_field_name?: string | undefined; over_field_value?: string | undefined; partition_field_name?: string | undefined; partition_field_value?: string | undefined; record_score?: number | undefined; timestamp?: string | number | undefined; typical?: number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & { 'kibana.alert.datafeed_results'?: { datafeed_id?: string | undefined; datafeed_state?: string | undefined; job_id?: string | undefined; job_state?: string | undefined; }[] | undefined; 'kibana.alert.delayed_data_results'?: { annotation?: string | undefined; end_timestamp?: string | number | undefined; job_id?: string | undefined; missed_docs_count?: string | number | undefined; }[] | undefined; 'kibana.alert.job_errors_results'?: { errors?: unknown; job_id?: string | undefined; }[] | undefined; 'kibana.alert.mml_results'?: { job_id?: string | undefined; log_time?: string | number | undefined; memory_status?: string | undefined; model_bytes?: string | number | undefined; model_bytes_exceeded?: string | number | undefined; model_bytes_memory_limit?: string | number | undefined; peak_model_bytes?: string | number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }) | ({} & { 'kibana.alert.results'?: { description?: string | undefined; health_status?: string | undefined; issues?: unknown; node_name?: string | undefined; transform_id?: string | undefined; transform_state?: string | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; })" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/index.ts", "deprecated": false, @@ -211,7 +211,7 @@ "label": "Alert", "description": [], "signature": [ - "{ '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" + "{ '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/alert_schema.ts", "deprecated": false, @@ -249,7 +249,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.intended_timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.original\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + "[]; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.intended_timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.original\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" ], "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", "deprecated": false, @@ -264,7 +264,7 @@ "label": "DefaultAlert", "description": [], "signature": [ - "{} & {} & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" + "{} & {} & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/default_schema.ts", "deprecated": false, @@ -330,7 +330,7 @@ "label": "MlAnomalyDetectionAlert", "description": [], "signature": [ - "{ 'kibana.alert.job_id': string; } & { 'kibana.alert.anomaly_score'?: number[] | undefined; 'kibana.alert.anomaly_timestamp'?: string | number | undefined; 'kibana.alert.is_interim'?: boolean | undefined; 'kibana.alert.top_influencers'?: { influencer_field_name?: string | undefined; influencer_field_value?: string | undefined; influencer_score?: number | undefined; initial_influencer_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; timestamp?: string | number | undefined; }[] | undefined; 'kibana.alert.top_records'?: { actual?: number | undefined; by_field_name?: string | undefined; by_field_value?: string | undefined; detector_index?: number | undefined; field_name?: string | undefined; function?: string | undefined; initial_record_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; over_field_name?: string | undefined; over_field_value?: string | undefined; partition_field_name?: string | undefined; partition_field_value?: string | undefined; record_score?: number | undefined; timestamp?: string | number | undefined; typical?: number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" + "{ 'kibana.alert.job_id': string; } & { 'kibana.alert.anomaly_score'?: number[] | undefined; 'kibana.alert.anomaly_timestamp'?: string | number | undefined; 'kibana.alert.is_interim'?: boolean | undefined; 'kibana.alert.top_influencers'?: { influencer_field_name?: string | undefined; influencer_field_value?: string | undefined; influencer_score?: number | undefined; initial_influencer_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; timestamp?: string | number | undefined; }[] | undefined; 'kibana.alert.top_records'?: { actual?: number | undefined; by_field_name?: string | undefined; by_field_value?: string | undefined; detector_index?: number | undefined; field_name?: string | undefined; function?: string | undefined; initial_record_score?: number | undefined; is_interim?: boolean | undefined; job_id?: string | undefined; over_field_name?: string | undefined; over_field_value?: string | undefined; partition_field_name?: string | undefined; partition_field_value?: string | undefined; record_score?: number | undefined; timestamp?: string | number | undefined; typical?: number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/ml_anomaly_detection_schema.ts", "deprecated": false, @@ -345,7 +345,7 @@ "label": "MlAnomalyDetectionHealthAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.datafeed_results'?: { datafeed_id?: string | undefined; datafeed_state?: string | undefined; job_id?: string | undefined; job_state?: string | undefined; }[] | undefined; 'kibana.alert.delayed_data_results'?: { annotation?: string | undefined; end_timestamp?: string | number | undefined; job_id?: string | undefined; missed_docs_count?: string | number | undefined; }[] | undefined; 'kibana.alert.job_errors_results'?: { errors?: unknown; job_id?: string | undefined; }[] | undefined; 'kibana.alert.mml_results'?: { job_id?: string | undefined; log_time?: string | number | undefined; memory_status?: string | undefined; model_bytes?: string | number | undefined; model_bytes_exceeded?: string | number | undefined; model_bytes_memory_limit?: string | number | undefined; peak_model_bytes?: string | number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" + "{} & { 'kibana.alert.datafeed_results'?: { datafeed_id?: string | undefined; datafeed_state?: string | undefined; job_id?: string | undefined; job_state?: string | undefined; }[] | undefined; 'kibana.alert.delayed_data_results'?: { annotation?: string | undefined; end_timestamp?: string | number | undefined; job_id?: string | undefined; missed_docs_count?: string | number | undefined; }[] | undefined; 'kibana.alert.job_errors_results'?: { errors?: unknown; job_id?: string | undefined; }[] | undefined; 'kibana.alert.mml_results'?: { job_id?: string | undefined; log_time?: string | number | undefined; memory_status?: string | undefined; model_bytes?: string | number | undefined; model_bytes_exceeded?: string | number | undefined; model_bytes_memory_limit?: string | number | undefined; peak_model_bytes?: string | number | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/ml_anomaly_detection_health_schema.ts", "deprecated": false, @@ -360,7 +360,7 @@ "label": "ObservabilityApmAlert", "description": [], "signature": [ - "{} & { 'agent.name'?: string | undefined; 'container.id'?: string | undefined; 'error.grouping_key'?: string | undefined; 'error.grouping_name'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'processor.event'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.name'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" + "{} & { 'agent.name'?: string | undefined; 'container.id'?: string | undefined; 'error.grouping_key'?: string | undefined; 'error.grouping_name'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'processor.event'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.name'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/observability_apm_schema.ts", "deprecated": false, @@ -375,7 +375,7 @@ "label": "ObservabilityLogsAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" + "{} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/observability_logs_schema.ts", "deprecated": false, @@ -390,7 +390,7 @@ "label": "ObservabilityMetricsAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" + "{} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/observability_metrics_schema.ts", "deprecated": false, @@ -405,7 +405,7 @@ "label": "ObservabilitySloAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; 'slo.id'?: string | undefined; 'slo.instanceId'?: string | undefined; 'slo.revision'?: string | number | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" + "{} & { 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; 'slo.id'?: string | undefined; 'slo.instanceId'?: string | undefined; 'slo.revision'?: string | number | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/observability_slo_schema.ts", "deprecated": false, @@ -420,7 +420,7 @@ "label": "ObservabilityUptimeAlert", "description": [], "signature": [ - "{} & { 'agent.name'?: string | undefined; 'anomaly.bucket_span.minutes'?: string | undefined; 'anomaly.start'?: string | number | undefined; configId?: string | undefined; 'error.message'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'location.id'?: string[] | undefined; 'location.name'?: string[] | undefined; 'monitor.id'?: string | undefined; 'monitor.name'?: string | undefined; 'monitor.state.id'?: string | undefined; 'monitor.tags'?: string[] | undefined; 'monitor.type'?: string | undefined; 'observer.geo.name'?: string[] | undefined; 'observer.name'?: string[] | undefined; 'service.name'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.x509.issuer.common_name'?: string | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.subject.common_name'?: string | undefined; 'url.full'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" + "{} & { 'agent.name'?: string | undefined; 'anomaly.bucket_span.minutes'?: string | undefined; 'anomaly.start'?: string | number | undefined; configId?: string | undefined; 'error.message'?: string | undefined; 'host.name'?: string | undefined; 'kibana.alert.context'?: unknown; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | number | undefined; 'kibana.alert.evaluation.values'?: (string | number)[] | undefined; 'kibana.alert.group'?: { field?: string[] | undefined; value?: string[] | undefined; }[] | undefined; labels?: unknown; 'location.id'?: string[] | undefined; 'location.name'?: string[] | undefined; 'monitor.id'?: string | undefined; 'monitor.name'?: string | undefined; 'monitor.state.id'?: string | undefined; 'monitor.tags'?: string[] | undefined; 'monitor.type'?: string | undefined; 'observer.geo.name'?: string[] | undefined; 'observer.name'?: string[] | undefined; 'service.name'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.x509.issuer.common_name'?: string | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.subject.common_name'?: string | undefined; 'url.full'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/observability_uptime_schema.ts", "deprecated": false, @@ -435,7 +435,7 @@ "label": "SecurityAlert", "description": [], "signature": [ - "{ '@timestamp': string | number; 'kibana.alert.ancestors': { depth: string | number; id: string; index: string; type: string; }[]; 'kibana.alert.depth': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.original_event.action': string; 'kibana.alert.original_event.category': string[]; 'kibana.alert.original_event.created': string | number; 'kibana.alert.original_event.dataset': string; 'kibana.alert.original_event.id': string; 'kibana.alert.original_event.ingested': string | number; 'kibana.alert.original_event.kind': string; 'kibana.alert.original_event.module': string; 'kibana.alert.original_event.original': string; 'kibana.alert.original_event.outcome': string; 'kibana.alert.original_event.provider': string; 'kibana.alert.original_event.sequence': string | number; 'kibana.alert.original_event.type': string[]; 'kibana.alert.original_time': string | number; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.false_positives': string[]; 'kibana.alert.rule.max_signals': (string | number)[]; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.threat.framework': string; 'kibana.alert.rule.threat.tactic.id': string; 'kibana.alert.rule.threat.tactic.name': string; 'kibana.alert.rule.threat.tactic.reference': string; 'kibana.alert.rule.threat.technique.id': string; 'kibana.alert.rule.threat.technique.name': string; 'kibana.alert.rule.threat.technique.reference': string; 'kibana.alert.rule.threat.technique.subtechnique.id': string; 'kibana.alert.rule.threat.technique.subtechnique.name': string; 'kibana.alert.rule.threat.technique.subtechnique.reference': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'ecs.version'?: string | undefined; 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'host.asset.criticality'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.ancestors.rule'?: string | undefined; 'kibana.alert.building_block_type'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.group.id'?: string | undefined; 'kibana.alert.group.index'?: number | undefined; 'kibana.alert.host.criticality_level'?: string | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.new_terms'?: string[] | undefined; 'kibana.alert.original_event.agent_id_status'?: string | undefined; 'kibana.alert.original_event.code'?: string | undefined; 'kibana.alert.original_event.duration'?: string | undefined; 'kibana.alert.original_event.end'?: string | number | undefined; 'kibana.alert.original_event.hash'?: string | undefined; 'kibana.alert.original_event.reason'?: string | undefined; 'kibana.alert.original_event.reference'?: string | undefined; 'kibana.alert.original_event.risk_score'?: number | undefined; 'kibana.alert.original_event.risk_score_norm'?: number | undefined; 'kibana.alert.original_event.severity'?: string | number | undefined; 'kibana.alert.original_event.start'?: string | number | undefined; 'kibana.alert.original_event.timezone'?: string | undefined; 'kibana.alert.original_event.url'?: string | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.building_block_type'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.immutable'?: string[] | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.rule.timeline_id'?: string[] | undefined; 'kibana.alert.rule.timeline_title'?: string[] | undefined; 'kibana.alert.rule.timestamp_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.threshold_result.cardinality'?: unknown; 'kibana.alert.threshold_result.count'?: string | number | undefined; 'kibana.alert.threshold_result.from'?: string | number | undefined; 'kibana.alert.threshold_result.terms'?: { field?: string | undefined; value?: string | undefined; }[] | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.user.criticality_level'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.alert.workflow_user'?: string | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; 'user.asset.criticality'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" + "{ '@timestamp': string | number; 'kibana.alert.ancestors': { depth: string | number; id: string; index: string; type: string; }[]; 'kibana.alert.depth': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.original_event.action': string; 'kibana.alert.original_event.category': string[]; 'kibana.alert.original_event.created': string | number; 'kibana.alert.original_event.dataset': string; 'kibana.alert.original_event.id': string; 'kibana.alert.original_event.ingested': string | number; 'kibana.alert.original_event.kind': string; 'kibana.alert.original_event.module': string; 'kibana.alert.original_event.original': string; 'kibana.alert.original_event.outcome': string; 'kibana.alert.original_event.provider': string; 'kibana.alert.original_event.sequence': string | number; 'kibana.alert.original_event.type': string[]; 'kibana.alert.original_time': string | number; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.false_positives': string[]; 'kibana.alert.rule.max_signals': (string | number)[]; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.threat.framework': string; 'kibana.alert.rule.threat.tactic.id': string; 'kibana.alert.rule.threat.tactic.name': string; 'kibana.alert.rule.threat.tactic.reference': string; 'kibana.alert.rule.threat.technique.id': string; 'kibana.alert.rule.threat.technique.name': string; 'kibana.alert.rule.threat.technique.reference': string; 'kibana.alert.rule.threat.technique.subtechnique.id': string; 'kibana.alert.rule.threat.technique.subtechnique.name': string; 'kibana.alert.rule.threat.technique.subtechnique.reference': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'ecs.version'?: string | undefined; 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'host.asset.criticality'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.ancestors.rule'?: string | undefined; 'kibana.alert.building_block_type'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.group.id'?: string | undefined; 'kibana.alert.group.index'?: number | undefined; 'kibana.alert.host.criticality_level'?: string | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.new_terms'?: string[] | undefined; 'kibana.alert.original_event.agent_id_status'?: string | undefined; 'kibana.alert.original_event.code'?: string | undefined; 'kibana.alert.original_event.duration'?: string | undefined; 'kibana.alert.original_event.end'?: string | number | undefined; 'kibana.alert.original_event.hash'?: string | undefined; 'kibana.alert.original_event.reason'?: string | undefined; 'kibana.alert.original_event.reference'?: string | undefined; 'kibana.alert.original_event.risk_score'?: number | undefined; 'kibana.alert.original_event.risk_score_norm'?: number | undefined; 'kibana.alert.original_event.severity'?: string | number | undefined; 'kibana.alert.original_event.start'?: string | number | undefined; 'kibana.alert.original_event.timezone'?: string | undefined; 'kibana.alert.original_event.url'?: string | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.building_block_type'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.immutable'?: string[] | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.rule.timeline_id'?: string[] | undefined; 'kibana.alert.rule.timeline_title'?: string[] | undefined; 'kibana.alert.rule.timestamp_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.threshold_result.cardinality'?: unknown; 'kibana.alert.threshold_result.count'?: string | number | undefined; 'kibana.alert.threshold_result.from'?: string | number | undefined; 'kibana.alert.threshold_result.terms'?: { field?: string | undefined; value?: string | undefined; }[] | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.user.criticality_level'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.alert.workflow_user'?: string | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; 'user.asset.criticality'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; } & {} & { 'ecs.version'?: string | undefined; 'kibana.alert.risk_score'?: number | undefined; 'kibana.alert.rule.author'?: string | undefined; 'kibana.alert.rule.created_at'?: string | number | undefined; 'kibana.alert.rule.created_by'?: string | undefined; 'kibana.alert.rule.description'?: string | undefined; 'kibana.alert.rule.enabled'?: string | undefined; 'kibana.alert.rule.from'?: string | undefined; 'kibana.alert.rule.interval'?: string | undefined; 'kibana.alert.rule.license'?: string | undefined; 'kibana.alert.rule.note'?: string | undefined; 'kibana.alert.rule.references'?: string[] | undefined; 'kibana.alert.rule.rule_id'?: string | undefined; 'kibana.alert.rule.rule_name_override'?: string | undefined; 'kibana.alert.rule.to'?: string | undefined; 'kibana.alert.rule.type'?: string | undefined; 'kibana.alert.rule.updated_at'?: string | number | undefined; 'kibana.alert.rule.updated_by'?: string | undefined; 'kibana.alert.rule.version'?: string | undefined; 'kibana.alert.severity'?: string | undefined; 'kibana.alert.suppression.docs_count'?: string | number | undefined; 'kibana.alert.suppression.end'?: string | number | undefined; 'kibana.alert.suppression.start'?: string | number | undefined; 'kibana.alert.suppression.terms.field'?: string[] | undefined; 'kibana.alert.suppression.terms.value'?: string[] | undefined; 'kibana.alert.system_status'?: string | undefined; 'kibana.alert.workflow_reason'?: string | undefined; 'kibana.alert.workflow_status_updated_at'?: string | number | undefined; 'kibana.alert.workflow_user'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/security_schema.ts", "deprecated": false, @@ -450,7 +450,7 @@ "label": "StackAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.evaluation.conditions'?: string | undefined; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | undefined; 'kibana.alert.title'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; }" + "{} & { 'kibana.alert.evaluation.conditions'?: string | undefined; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | undefined; 'kibana.alert.title'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/stack_schema.ts", "deprecated": false, @@ -465,7 +465,7 @@ "label": "TransformHealthAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.results'?: { description?: string | undefined; health_status?: string | undefined; issues?: unknown; node_name?: string | undefined; transform_id?: string | undefined; transform_state?: string | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" + "{} & { 'kibana.alert.results'?: { description?: string | undefined; health_status?: string | undefined; issues?: unknown; node_name?: string | undefined; transform_id?: string | undefined; transform_state?: string | undefined; }[] | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'event.original'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.intended_timestamp'?: string | number | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.previous_action_group'?: string | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.type'?: string | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.severity_improving'?: boolean | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/transform_health_schema.ts", "deprecated": false, @@ -490,7 +490,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.intended_timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.original\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + "[]; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.intended_timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"event.original\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; readonly ignore_above: 1024; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" ], "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", "deprecated": false, diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 0294ba0d59ad5..65f0ce81826bc 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_grouping.mdx b/api_docs/kbn_alerts_grouping.mdx index e41d25ab9a4a4..00846e7fc4768 100644 --- a/api_docs/kbn_alerts_grouping.mdx +++ b/api_docs/kbn_alerts_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-grouping title: "@kbn/alerts-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-grouping plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-grouping'] --- import kbnAlertsGroupingObj from './kbn_alerts_grouping.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 42908fa61c79b..7a037db0d46a8 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 0f712f2d31dea..009963f13d19e 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 7108fe83f9299..e8857588ee923 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 675d7ec6443e3..16a63ed17f15a 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index 685df0e98d62d..6a0f75b88bdac 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.devdocs.json b/api_docs/kbn_apm_synthtrace.devdocs.json index 3027c0ac271a4..22aba43db2ba7 100644 --- a/api_docs/kbn_apm_synthtrace.devdocs.json +++ b/api_docs/kbn_apm_synthtrace.devdocs.json @@ -771,6 +771,87 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.LogsSynthtraceEsClient.createComponentTemplate", + "type": "Function", + "tags": [], + "label": "createComponentTemplate", + "description": [], + "signature": [ + "(name: string, mappings: ", + "MappingTypeMapping", + ") => Promise" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.LogsSynthtraceEsClient.createComponentTemplate.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.LogsSynthtraceEsClient.createComponentTemplate.$2", + "type": "Object", + "tags": [], + "label": "mappings", + "description": [], + "signature": [ + "MappingTypeMapping" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.LogsSynthtraceEsClient.deleteComponentTemplate", + "type": "Function", + "tags": [], + "label": "deleteComponentTemplate", + "description": [], + "signature": [ + "(name: string) => Promise" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.LogsSynthtraceEsClient.deleteComponentTemplate.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/apm-synthtrace", "id": "def-server.LogsSynthtraceEsClient.createIndex", @@ -984,6 +1065,77 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.OtelSynthtraceEsClient", + "type": "Class", + "tags": [], + "label": "OtelSynthtraceEsClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-synthtrace", + "scope": "server", + "docId": "kibKbnApmSynthtracePluginApi", + "section": "def-server.OtelSynthtraceEsClient", + "text": "OtelSynthtraceEsClient" + }, + " extends ", + "SynthtraceEsClient", + "<", + { + "pluginId": "@kbn/apm-synthtrace-client", + "scope": "common", + "docId": "kibKbnApmSynthtraceClientPluginApi", + "section": "def-common.OtelDocument", + "text": "OtelDocument" + }, + ">" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/otel/otel_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.OtelSynthtraceEsClient.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/otel/otel_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.OtelSynthtraceEsClient.Unnamed.$1", + "type": "CompoundType", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ client: ", + "default", + "; logger: ", + "Logger", + "; } & ", + "OtelSynthtraceEsClientOptions" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/otel/otel_synthtrace_es_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/apm-synthtrace", "id": "def-server.SyntheticsSynthtraceEsClient", diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index add4887215bae..90249cf7abb01 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 64 | 0 | 64 | 10 | +| 72 | 0 | 72 | 11 | ## Server diff --git a/api_docs/kbn_apm_synthtrace_client.devdocs.json b/api_docs/kbn_apm_synthtrace_client.devdocs.json index 07cd4c0009b16..ce31ed4d3998e 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -2793,6 +2793,212 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument", + "type": "Interface", + "tags": [], + "label": "OtelDocument", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-synthtrace-client", + "scope": "common", + "docId": "kibKbnApmSynthtraceClientPluginApi", + "section": "def-common.OtelDocument", + "text": "OtelDocument" + }, + " extends { '@timestamp'?: number | undefined; }" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.data_stream", + "type": "Object", + "tags": [], + "label": "data_stream", + "description": [], + "signature": [ + "{ dataset: string; namespace: string; type: string; } | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.attributes", + "type": "Object", + "tags": [], + "label": "attributes", + "description": [], + "signature": [ + "{ [key: string]: any; 'timestamp.us'?: number | undefined; 'metricset.name'?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.resource", + "type": "Object", + "tags": [], + "label": "resource", + "description": [], + "signature": [ + "{ attributes?: OtelSharedResourceAttributes | undefined; dropped_attributes_count?: number | undefined; schema_url?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.scope", + "type": "Object", + "tags": [], + "label": "scope", + "description": [], + "signature": [ + "{ attributes?: { 'service.framework.name'?: string | undefined; 'service.framework.version'?: string | undefined; } | undefined; dropped_attributes_count?: number | undefined; name?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.trace_id", + "type": "string", + "tags": [], + "label": "trace_id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.trace", + "type": "Object", + "tags": [], + "label": "trace", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.span_id", + "type": "string", + "tags": [], + "label": "span_id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.span", + "type": "Object", + "tags": [], + "label": "span", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.dropped_attributes_count", + "type": "number", + "tags": [], + "label": "dropped_attributes_count", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.dropped_events_count", + "type": "number", + "tags": [], + "label": "dropped_events_count", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.dropped_links_count", + "type": "number", + "tags": [], + "label": "dropped_links_count", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.OtelDocument.timestamp_us", + "type": "number", + "tags": [], + "label": "timestamp_us", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "enums": [], @@ -3607,6 +3813,48 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.otel", + "type": "Object", + "tags": [], + "label": "otel", + "description": [], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.otel.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "(id: string) => Otel" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.otel.create.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-apm-synthtrace-client/src/lib/otel/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/apm-synthtrace-client", "id": "def-common.syntheticsMonitor", diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index d14e619cdd6f4..a5b8b880cca05 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 223 | 0 | 223 | 36 | +| 240 | 0 | 240 | 36 | ## Common diff --git a/api_docs/kbn_apm_types.mdx b/api_docs/kbn_apm_types.mdx index 08bfc37eb6303..4978738880351 100644 --- a/api_docs/kbn_apm_types.mdx +++ b/api_docs/kbn_apm_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-types title: "@kbn/apm-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-types'] --- import kbnApmTypesObj from './kbn_apm_types.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 722c1c309b985..9ad8bf5d725bd 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_avc_banner.mdx b/api_docs/kbn_avc_banner.mdx index 50ce155ded90c..551376161fa77 100644 --- a/api_docs/kbn_avc_banner.mdx +++ b/api_docs/kbn_avc_banner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-avc-banner title: "@kbn/avc-banner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/avc-banner plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/avc-banner'] --- import kbnAvcBannerObj from './kbn_avc_banner.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index b9e18c776e376..42e91eb967bc7 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 60b95bb658abc..6905b9a4664c2 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index 755afbc8c528c..79545872b2bca 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index ecd3c779fe6bb..e6de699b450fa 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 0f77c0ab99a88..6b0d991e01bdc 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cbor.mdx b/api_docs/kbn_cbor.mdx index 148363f86297e..ab538cdd46011 100644 --- a/api_docs/kbn_cbor.mdx +++ b/api_docs/kbn_cbor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cbor title: "@kbn/cbor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cbor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cbor'] --- import kbnCborObj from './kbn_cbor.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index cd3d53813b787..456c8afef54d6 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index 1eaaba5afa893..7aef0b077275a 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 99f4a45155ed3..c88ad065a6828 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index f59aec229d23f..9044f22488f03 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index db815b7232968..9201aa2fe3f9e 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 340f2eaab560b..bd94e200d782d 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index da4ae61b8c448..70b0938a1d71f 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_cloud_security_posture.devdocs.json b/api_docs/kbn_cloud_security_posture.devdocs.json index 0159004e318ba..89432b1285c4a 100644 --- a/api_docs/kbn_cloud_security_posture.devdocs.json +++ b/api_docs/kbn_cloud_security_posture.devdocs.json @@ -13,7 +13,7 @@ "signature": [ "({ type }: Props) => React.JSX.Element" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/components/csp_evaluation_badge.tsx", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/components/csp_evaluation_badge.tsx", "deprecated": false, "trackAdoption": false, "children": [ @@ -27,7 +27,7 @@ "signature": [ "Props" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/components/csp_evaluation_badge.tsx", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/components/csp_evaluation_badge.tsx", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -46,7 +46,7 @@ "signature": [ "({ score, version }: CVSScoreBadgeProps) => React.JSX.Element | null" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/components/vulnerability_badges.tsx", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/components/vulnerability_badges.tsx", "deprecated": false, "trackAdoption": false, "children": [ @@ -60,7 +60,7 @@ "signature": [ "CVSScoreBadgeProps" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/components/vulnerability_badges.tsx", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/components/vulnerability_badges.tsx", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -79,7 +79,7 @@ "signature": [ "(search?: string | undefined) => Partial | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/query_utils.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/query_utils.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -93,7 +93,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/query_utils.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/query_utils.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -112,7 +112,7 @@ "signature": [ "(query: any) => string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/query_utils.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/query_utils.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -126,7 +126,7 @@ "signature": [ "any" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/query_utils.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/query_utils.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -145,7 +145,7 @@ "signature": [ "(score: number) => string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/get_vulnerability_colors.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -159,7 +159,7 @@ "signature": [ "number" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/get_vulnerability_colors.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -186,7 +186,7 @@ }, ") => string" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/get_vulnerability_colors.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -206,7 +206,7 @@ "text": "VulnSeverity" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/get_vulnerability_colors.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -233,7 +233,7 @@ }, ") => string" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/get_vulnerability_text.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -253,7 +253,7 @@ "text": "VulnSeverity" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/get_vulnerability_text.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -272,7 +272,7 @@ "signature": [ "(counts: VulnerabilityCounts) => VulnerabilitiesDistributionBarProps[]" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/vulnerability_helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -286,7 +286,7 @@ "signature": [ "VulnerabilityCounts" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/vulnerability_helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -305,7 +305,7 @@ "signature": [ "(counts: VulnerabilityCounts) => boolean" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/vulnerability_helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -319,7 +319,7 @@ "signature": [ "VulnerabilityCounts" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/vulnerability_helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -338,7 +338,7 @@ "signature": [ "({ severity }: SeverityStatusBadgeProps) => React.JSX.Element | null" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/components/vulnerability_badges.tsx", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/components/vulnerability_badges.tsx", "deprecated": false, "trackAdoption": false, "children": [ @@ -352,7 +352,7 @@ "signature": [ "SeverityStatusBadgeProps" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/components/vulnerability_badges.tsx", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/components/vulnerability_badges.tsx", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -379,7 +379,7 @@ }, ", error: unknown) => void" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/show_error_toast.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/show_error_toast.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -399,7 +399,7 @@ "text": "IToasts" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/show_error_toast.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/show_error_toast.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -414,7 +414,7 @@ "signature": [ "unknown" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/utils/show_error_toast.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/utils/show_error_toast.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -432,7 +432,7 @@ "tags": [], "label": "CspBaseEsQuery", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -448,7 +448,7 @@ "QueryDslQueryContainer", "[]; }; } | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false } @@ -462,7 +462,7 @@ "tags": [], "label": "CspClientPluginStartDeps", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -482,7 +482,7 @@ "text": "DataPublicPluginStart" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -502,7 +502,7 @@ "text": "DataViewsServicePublic" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -522,7 +522,7 @@ "text": "PluginStart" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -542,7 +542,7 @@ "text": "UnifiedSearchPublicPluginStart" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -630,7 +630,7 @@ }, "; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -660,7 +660,7 @@ }, "; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -680,7 +680,7 @@ "text": "IToasts" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -703,7 +703,7 @@ "ActiveCursor", "; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -723,7 +723,7 @@ "text": "DiscoverStart" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -743,7 +743,7 @@ "text": "FleetStart" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -763,7 +763,7 @@ "text": "LicensingPluginStart" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -803,7 +803,7 @@ }, ">): void; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -823,7 +823,7 @@ "text": "Storage" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -843,7 +843,7 @@ "text": "SpacesApi" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -863,7 +863,7 @@ "text": "CloudSetup" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -884,7 +884,7 @@ }, " | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false } @@ -898,7 +898,7 @@ "tags": [], "label": "FindingsAggs", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -915,7 +915,7 @@ "AggregationsStringRareTermsBucketKeys", ">" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false } @@ -929,7 +929,7 @@ "tags": [], "label": "FindingsBaseEsQuery", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -951,7 +951,7 @@ }, "; } | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false } @@ -982,7 +982,7 @@ "text": "CspBaseEsQuery" } ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -996,7 +996,7 @@ "signature": [ "string[][]" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1007,7 +1007,7 @@ "tags": [], "label": "enabled", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1018,7 +1018,7 @@ "tags": [], "label": "pageSize", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1032,7 +1032,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false } @@ -1061,7 +1061,7 @@ "SearchRequest", ">" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1101,7 +1101,7 @@ }, ">>" ], - "path": "x-pack/packages/kbn-cloud-security-posture/type.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1116,7 +1116,7 @@ "signature": [ "{ [x: string]: FilterValue; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture/src/hooks/use_navigate_findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_navigate_findings.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1130,7 +1130,7 @@ "tags": [], "label": "findingsNavigation", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1141,7 +1141,7 @@ "tags": [], "label": "findings_default", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1152,7 +1152,7 @@ "tags": [], "label": "name", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1163,7 +1163,7 @@ "tags": [], "label": "path", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1174,7 +1174,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1187,7 +1187,7 @@ "tags": [], "label": "findings_by_resource", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1198,7 +1198,7 @@ "tags": [], "label": "name", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1209,7 +1209,7 @@ "tags": [], "label": "path", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1220,7 +1220,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1233,7 +1233,7 @@ "tags": [], "label": "resource_findings", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1244,7 +1244,7 @@ "tags": [], "label": "name", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1255,7 +1255,7 @@ "tags": [], "label": "path", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1266,7 +1266,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1279,7 +1279,7 @@ "tags": [], "label": "vulnerabilities", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1290,7 +1290,7 @@ "tags": [], "label": "name", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1301,7 +1301,7 @@ "tags": [], "label": "path", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1312,7 +1312,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1325,7 +1325,7 @@ "tags": [], "label": "vulnerabilities_by_resource", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1336,7 +1336,7 @@ "tags": [], "label": "name", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1347,7 +1347,7 @@ "tags": [], "label": "path", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1358,7 +1358,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1371,7 +1371,7 @@ "tags": [], "label": "resource_vulnerabilities", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1382,7 +1382,7 @@ "tags": [], "label": "name", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1393,7 +1393,7 @@ "tags": [], "label": "path", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1404,7 +1404,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1420,7 +1420,7 @@ "tags": [], "label": "NAV_ITEMS_NAMES", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1431,7 +1431,7 @@ "tags": [], "label": "DASHBOARD", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1442,7 +1442,7 @@ "tags": [], "label": "VULNERABILITY_DASHBOARD", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1453,7 +1453,7 @@ "tags": [], "label": "FINDINGS", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1464,7 +1464,7 @@ "tags": [], "label": "BENCHMARKS", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false }, @@ -1475,7 +1475,7 @@ "tags": [], "label": "RULES", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/navigation.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/navigation.ts", "deprecated": false, "trackAdoption": false } @@ -1489,7 +1489,7 @@ "tags": [], "label": "statusColors", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/component_constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1500,7 +1500,7 @@ "tags": [], "label": "passed", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/component_constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts", "deprecated": false, "trackAdoption": false }, @@ -1511,7 +1511,7 @@ "tags": [], "label": "failed", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture/constants/component_constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts", "deprecated": false, "trackAdoption": false } diff --git a/api_docs/kbn_cloud_security_posture.mdx b/api_docs/kbn_cloud_security_posture.mdx index 3a2f81a7f6f60..3d55e1cf928c5 100644 --- a/api_docs/kbn_cloud_security_posture.mdx +++ b/api_docs/kbn_cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cloud-security-posture title: "@kbn/cloud-security-posture" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cloud-security-posture plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cloud-security-posture'] --- import kbnCloudSecurityPostureObj from './kbn_cloud_security_posture.devdocs.json'; diff --git a/api_docs/kbn_cloud_security_posture_common.devdocs.json b/api_docs/kbn_cloud_security_posture_common.devdocs.json index 3a11e83ae49f2..7a23e5f8cfbd0 100644 --- a/api_docs/kbn_cloud_security_posture_common.devdocs.json +++ b/api_docs/kbn_cloud_security_posture_common.devdocs.json @@ -25,7 +25,7 @@ "tags": [], "label": "UiMetricService", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/ui_metrics.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -47,7 +47,7 @@ }, ") => void" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/ui_metrics.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -67,7 +67,7 @@ "text": "UsageCollectionSetup" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/ui_metrics.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -85,7 +85,7 @@ "signature": [ "(metricType: string, eventName: CloudSecurityUiCounters) => void" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/ui_metrics.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -99,7 +99,7 @@ "signature": [ "string" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/ui_metrics.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -114,7 +114,7 @@ "signature": [ "CloudSecurityUiCounters" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/ui_metrics.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -137,7 +137,7 @@ "signature": [ "(field: string, queryValue?: string | undefined) => { bool: { filter: { bool: { should: { term: { [x: string]: string; }; }[]; minimum_should_match: number; }; }[]; }; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -151,7 +151,7 @@ "signature": [ "string" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -166,7 +166,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -187,7 +187,7 @@ "QueryDslQueryContainer", "[]" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -201,7 +201,7 @@ "signature": [ "Record>" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -220,7 +220,7 @@ "signature": [ "(e: unknown, fallbackMessage?: string | undefined) => string" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -234,7 +234,7 @@ "signature": [ "unknown" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -249,7 +249,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -270,7 +270,7 @@ "signature": [ "(value: number) => string | number" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/get_abbreviated_number.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/get_abbreviated_number.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -284,7 +284,7 @@ "signature": [ "number" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/utils/get_abbreviated_number.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/utils/get_abbreviated_number.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -302,7 +302,7 @@ "tags": [], "label": "BaseCspSetupBothPolicy", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -316,7 +316,7 @@ "signature": [ "\"indexed\" | \"unprivileged\" | \"indexing\" | \"index-timeout\" | \"not-deployed\" | \"not-installed\" | \"waiting_for_results\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -327,7 +327,7 @@ "tags": [], "label": "installedPackagePolicies", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -338,7 +338,7 @@ "tags": [], "label": "healthyAgents", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false } @@ -352,7 +352,7 @@ "tags": [], "label": "BaseCspSetupStatus", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -373,7 +373,7 @@ }, "[]" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -384,7 +384,7 @@ "tags": [], "label": "latestPackageVersion", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -404,7 +404,7 @@ "text": "BaseCspSetupBothPolicy" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -424,7 +424,7 @@ "text": "BaseCspSetupBothPolicy" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -444,7 +444,7 @@ "text": "BaseCspSetupBothPolicy" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -455,7 +455,7 @@ "tags": [], "label": "isPluginInitialized", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -469,7 +469,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -483,7 +483,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -497,7 +497,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false } @@ -511,7 +511,7 @@ "tags": [], "label": "CspFinding", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -522,7 +522,7 @@ "tags": [], "label": "'@timestamp'", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -536,7 +536,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -550,7 +550,7 @@ "signature": [ "CspFindingOrchestrator | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -564,7 +564,7 @@ "signature": [ "CspFindingCloud | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -584,7 +584,7 @@ "text": "CspFindingResult" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -598,7 +598,7 @@ "signature": [ "CspFindingResource" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -610,9 +610,9 @@ "label": "rule", "description": [], "signature": [ - "{ readonly references?: string | undefined; readonly impact?: string | undefined; readonly default_value?: string | undefined; readonly id: string; readonly version: string; readonly name: string; readonly tags: string[]; readonly description: string; readonly section: string; readonly audit: string; readonly benchmark: Readonly<{ posture_type?: \"kspm\" | \"cspm\" | undefined; rule_number?: string | undefined; } & { id: string; version: string; name: string; }>; readonly profile_applicability: string; readonly rationale: string; readonly rego_rule_id: string; readonly remediation: string; }" + "{ readonly references?: string | undefined; readonly impact?: string | undefined; readonly reference?: string | undefined; readonly default_value?: string | undefined; readonly id: string; readonly version: string; readonly name: string; readonly tags: string[]; readonly description: string; readonly section: string; readonly audit: string; readonly benchmark: Readonly<{ posture_type?: \"kspm\" | \"cspm\" | undefined; rule_number?: string | undefined; } & { id: string; version: string; name: string; }>; readonly profile_applicability: string; readonly rationale: string; readonly rego_rule_id: string; readonly remediation: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -626,7 +626,7 @@ "signature": [ "CspFindingHost" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -640,7 +640,7 @@ "signature": [ "EcsEvent" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -654,7 +654,7 @@ "signature": [ "EcsDataStream" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -668,7 +668,7 @@ "signature": [ "EcsObserver" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -682,7 +682,7 @@ "signature": [ "CspFindingAgent" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -696,7 +696,7 @@ "signature": [ "{ version: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false } @@ -710,7 +710,7 @@ "tags": [], "label": "CspFindingResult", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -724,7 +724,7 @@ "signature": [ "\"failed\" | \"passed\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -738,7 +738,7 @@ "signature": [ "Record | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false }, @@ -752,7 +752,7 @@ "signature": [ "{ [x: string]: unknown; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/findings.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/findings.ts", "deprecated": false, "trackAdoption": false } @@ -766,7 +766,7 @@ "tags": [], "label": "CspVulnerabilityFinding", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -777,7 +777,7 @@ "tags": [], "label": "'@timestamp'", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -791,7 +791,7 @@ "signature": [ "{ id: string; name: string; } | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -805,7 +805,7 @@ "signature": [ "EcsEvent" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -825,7 +825,7 @@ "text": "Vulnerability" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -839,7 +839,7 @@ "signature": [ "{ version: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -853,7 +853,7 @@ "signature": [ "{ os: { name: string; kernel: string; codename: string; type: string; platform: string; version: string; family: string; }; id: string; name: string; containerized: boolean; ip: string[]; mac: string[]; hostname: string; architecture: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -867,7 +867,7 @@ "signature": [ "{ ephemeral_id: string; id: string; name: string; type: string; version: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -881,7 +881,7 @@ "signature": [ "{ image?: { id: string; } | undefined; provider?: string | undefined; instance?: { id: string; } | undefined; machine?: { type: string; } | undefined; region: string; availability_zone?: string | undefined; service?: { name: string; } | undefined; account?: { id: string; } | undefined; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -895,7 +895,7 @@ "signature": [ "{ version: string; commit_sha: string; commit_time: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -909,7 +909,7 @@ "signature": [ "{ version?: string | undefined; name?: string | undefined; fixed_version?: string | undefined; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -923,7 +923,7 @@ "signature": [ "EcsDataStream" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -937,7 +937,7 @@ "signature": [ "EcsObserver" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false } @@ -951,7 +951,7 @@ "tags": [], "label": "IndexDetails", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -962,7 +962,7 @@ "tags": [], "label": "index", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false }, @@ -976,7 +976,7 @@ "signature": [ "\"empty\" | \"not-empty\" | \"unprivileged\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false } @@ -990,7 +990,7 @@ "tags": [], "label": "Vulnerability", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1004,7 +1004,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1018,7 +1018,7 @@ "signature": [ "{ version?: string | undefined; base?: number | undefined; } | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1032,7 +1032,7 @@ "signature": [ "string[]" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1043,7 +1043,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1054,7 +1054,7 @@ "tags": [], "label": "title", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1065,7 +1065,7 @@ "tags": [], "label": "reference", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1086,7 +1086,7 @@ }, " | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1106,7 +1106,7 @@ "VectorScoreBase", " | undefined; } | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1120,7 +1120,7 @@ "signature": [ "{ ID: string; Name: string; URL: string; } | undefined" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1131,7 +1131,7 @@ "tags": [], "label": "enumeration", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1142,7 +1142,7 @@ "tags": [], "label": "description", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1153,7 +1153,7 @@ "tags": [], "label": "classification", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false }, @@ -1167,7 +1167,7 @@ "signature": [ "{ vendor: string; }" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/schema/vulnerabilities/csp_vulnerability_finding.ts", "deprecated": false, "trackAdoption": false } @@ -1187,7 +1187,7 @@ "signature": [ "\"cis_k8s\" | \"cis_azure\" | \"cis_aws\" | \"cis_eks\" | \"cis_gcp\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/benchmark.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/benchmark.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1202,7 +1202,7 @@ "signature": [ "\"90d\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1217,7 +1217,7 @@ "signature": [ "\"logs-cloud_security_posture.findings_latest-default\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1232,7 +1232,7 @@ "signature": [ "\"logs-cloud_security_posture.vulnerabilities_latest-default\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1247,7 +1247,7 @@ "signature": [ "\"security_solution-*.misconfiguration_latest\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1262,7 +1262,7 @@ "signature": [ "\"security_solution-*.vulnerability_latest\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1277,7 +1277,7 @@ "signature": [ "\"security_solution_cdr_latest_misconfigurations\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1292,7 +1292,7 @@ "signature": [ "\"Latest Cloud Security Misconfigurations\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1304,7 +1304,7 @@ "tags": [], "label": "CDR_MISCONFIGURATIONS_INDEX_PATTERN", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1316,7 +1316,7 @@ "tags": [], "label": "CDR_VULNERABILITIES_INDEX_PATTERN", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1333,7 +1333,7 @@ "signature": [ "\"/cloud_security_posture\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1348,7 +1348,7 @@ "signature": [ "\"1\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1363,7 +1363,7 @@ "signature": [ "\"/internal/cloud_security_posture/rules/_get_states\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1378,7 +1378,7 @@ "signature": [ "\"cspm\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1399,7 +1399,7 @@ "text": "BaseCspSetupStatus" } ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1414,7 +1414,7 @@ "signature": [ "\"indexed\" | \"unprivileged\" | \"indexing\" | \"index-timeout\" | \"not-deployed\" | \"not-installed\" | \"waiting_for_results\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1429,7 +1429,7 @@ "signature": [ "\"empty\" | \"not-empty\" | \"unprivileged\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/status.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/status.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1444,7 +1444,7 @@ "signature": [ "\"kspm\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1459,7 +1459,7 @@ "signature": [ "\"26h\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1474,7 +1474,7 @@ "signature": [ "\"3d\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1489,7 +1489,7 @@ "signature": [ "500" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1504,7 +1504,7 @@ "signature": [ "\"security-solution-default\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1519,7 +1519,7 @@ "signature": [ "\"1\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1534,7 +1534,7 @@ "signature": [ "\"/internal/cloud_security_posture/status\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1549,7 +1549,7 @@ "signature": [ "\"UNKNOWN\" | \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"CRITICAL\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/types/vulnerabilities.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/types/vulnerabilities.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1563,7 +1563,7 @@ "tags": [], "label": "VULNERABILITIES_SEVERITY", "description": [], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1577,7 +1577,7 @@ "signature": [ "\"LOW\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false }, @@ -1591,7 +1591,7 @@ "signature": [ "\"MEDIUM\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false }, @@ -1605,7 +1605,7 @@ "signature": [ "\"HIGH\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false }, @@ -1619,7 +1619,7 @@ "signature": [ "\"CRITICAL\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false }, @@ -1633,7 +1633,7 @@ "signature": [ "\"UNKNOWN\"" ], - "path": "x-pack/packages/kbn-cloud-security-posture-common/constants.ts", + "path": "x-pack/packages/kbn-cloud-security-posture/common/constants.ts", "deprecated": false, "trackAdoption": false } diff --git a/api_docs/kbn_cloud_security_posture_common.mdx b/api_docs/kbn_cloud_security_posture_common.mdx index 4d2fc344b7d7c..defe8d1cac4c7 100644 --- a/api_docs/kbn_cloud_security_posture_common.mdx +++ b/api_docs/kbn_cloud_security_posture_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cloud-security-posture-common title: "@kbn/cloud-security-posture-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cloud-security-posture-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cloud-security-posture-common'] --- import kbnCloudSecurityPostureCommonObj from './kbn_cloud_security_posture_common.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 4a0ead885a596..0333ca5e02680 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 17c175194bd60..99341b441efd7 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 464c6f2687014..4decee554ab7d 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 3b9b1267dd01c..198580090aa52 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 283bbbf031497..6a06478182f1f 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index b9d32621c5d46..a40b2a51a7edd 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 8f6d3605ee26c..02cd782e22f2b 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index f2eb54dd4a3dc..5c53b074a720c 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_insights_public.mdx b/api_docs/kbn_content_management_content_insights_public.mdx index f9bed25d80276..b60c1749dc73f 100644 --- a/api_docs/kbn_content_management_content_insights_public.mdx +++ b/api_docs/kbn_content_management_content_insights_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-insights-public title: "@kbn/content-management-content-insights-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-insights-public plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-insights-public'] --- import kbnContentManagementContentInsightsPublicObj from './kbn_content_management_content_insights_public.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_insights_server.mdx b/api_docs/kbn_content_management_content_insights_server.mdx index 065993a210f58..5aed1095a9283 100644 --- a/api_docs/kbn_content_management_content_insights_server.mdx +++ b/api_docs/kbn_content_management_content_insights_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-insights-server title: "@kbn/content-management-content-insights-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-insights-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-insights-server'] --- import kbnContentManagementContentInsightsServerObj from './kbn_content_management_content_insights_server.devdocs.json'; diff --git a/api_docs/kbn_content_management_favorites_public.mdx b/api_docs/kbn_content_management_favorites_public.mdx index 8982b1a0bffdb..7a6fe4ce743d5 100644 --- a/api_docs/kbn_content_management_favorites_public.mdx +++ b/api_docs/kbn_content_management_favorites_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-favorites-public title: "@kbn/content-management-favorites-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-favorites-public plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-favorites-public'] --- import kbnContentManagementFavoritesPublicObj from './kbn_content_management_favorites_public.devdocs.json'; diff --git a/api_docs/kbn_content_management_favorites_server.mdx b/api_docs/kbn_content_management_favorites_server.mdx index 773ea03fec1ac..993bdd3c2fde0 100644 --- a/api_docs/kbn_content_management_favorites_server.mdx +++ b/api_docs/kbn_content_management_favorites_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-favorites-server title: "@kbn/content-management-favorites-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-favorites-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-favorites-server'] --- import kbnContentManagementFavoritesServerObj from './kbn_content_management_favorites_server.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index e97a5ac8f84e5..f44d161264b2b 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index b9ccd0cb6d1b4..09790126a2666 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index 855fc70cfa7f4..834b5bcbe5112 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index 2694c544f291c..57c338f78747f 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx index d9c1b71066484..d49dd5d5f09fb 100644 --- a/api_docs/kbn_content_management_user_profiles.mdx +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-user-profiles title: "@kbn/content-management-user-profiles" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-user-profiles plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] --- import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index f853fae07542b..21fe87ddef189 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.devdocs.json b/api_docs/kbn_core_analytics_browser.devdocs.json index 19fe335c5b550..bf12ecdde55f0 100644 --- a/api_docs/kbn_core_analytics_browser.devdocs.json +++ b/api_docs/kbn_core_analytics_browser.devdocs.json @@ -766,6 +766,22 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" @@ -1386,6 +1402,38 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, { "plugin": "inventory", "path": "x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts" @@ -1796,14 +1844,14 @@ "plugin": "cloud", "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, - { - "plugin": "licensing", - "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" - }, { "plugin": "spaces", "path": "x-pack/plugins/spaces/public/analytics/register_analytics_context.ts" }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 03bf0313ce550..0652cf1a542e7 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 2ad0fadbe8a92..80c453e4bdd0f 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 1ccf01e1cedf1..8ee7dd853d588 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.devdocs.json b/api_docs/kbn_core_analytics_server.devdocs.json index bea1c16c40cc6..c86846503ec14 100644 --- a/api_docs/kbn_core_analytics_server.devdocs.json +++ b/api_docs/kbn_core_analytics_server.devdocs.json @@ -774,6 +774,22 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" @@ -1394,6 +1410,38 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, { "plugin": "inventory", "path": "x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts" @@ -1804,14 +1852,14 @@ "plugin": "cloud", "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, - { - "plugin": "licensing", - "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" - }, { "plugin": "spaces", "path": "x-pack/plugins/spaces/public/analytics/register_analytics_context.ts" }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index d9c82f59c1684..fd54f4059459f 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 858cb8520abee..1f9c2f2d6583d 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 51beb4e965656..7f82663fdacd6 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index fdf72fa69f893..99f90c886d930 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index f62ffb0c6f570..45cb49d9ea45f 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index f49b5c324af84..521ce7a2826e8 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 98c06f8fc3979..9312e73ab9f0b 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index f9c68cbfae952..c60c7980019d2 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 550d6c534e40b..aa86d8833b526 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 7eb3a9f89c30a..b70c6efa07094 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 10fba67717c33..8760317533baa 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 87420e926e14b..31ef1b5b5b8d1 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 7700f345b2ead..79c9b2235f4ab 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 31bb200a57cef..84957a672edd4 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 2b962547ccb30..fcadccc572a6e 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index da073c6ec6cdd..dcc30803b1c3a 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 14dd96c3c7a8d..db160e3e6f78c 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index d07a15c1db1a7..1a874498bccd4 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.devdocs.json b/api_docs/kbn_core_chrome_browser.devdocs.json index 5c630b0958c7d..4579bfc4930ac 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -3716,7 +3716,7 @@ "label": "AppDeepLinkId", "description": [], "signature": [ - "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchRelevance\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"enterpriseSearchRelevance:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchRelevance\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"enterpriseSearchRelevance:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" ], "path": "packages/core/chrome/core-chrome-browser/src/project_navigation.ts", "deprecated": false, diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 43228f2040659..71e4acc82ddf5 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 04da05f13686c..fb4f4637a6ded 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 802b7bd3cab4f..3e4aa829b4637 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index ebcdfd7780a19..0cf9f32367f29 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index da5ba6c1b7b07..7c98934b289cc 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 5eefd9681de20..0f12ce440eda5 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index bc1b511902749..9b942db3ae842 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index 6ccab132aec70..4136695a3f03c 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 1c5da227c793b..a62019ebc6ca1 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index b7a318ddd9794..ae2bdb1e13a18 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index a68e756ff5fa2..68c82ac1da68a 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index dade90d1f5823..e580784f3f7f7 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 10f5335204034..21767d502e544 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 54535011827e7..4c51e15630936 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index dee1aff648f7d..27ec3d7273df9 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 12cc560573bb9..0a1a8bf067355 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 745f089ac92c8..7891c08b89500 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 55f5cd9b76df4..c73e7fc0aed6d 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 191a40daa2e97..5a652180891b7 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 62e72a0c99956..9329ff7d3de6f 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 5b5180e7eb369..3751fe69e2c9b 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index d01b9e167820a..3474087fc6ddb 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 6be3b89ea4263..9fb2e8eb06e75 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index ccf38731eeea6..d62395126c900 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index d9b3b543d67ae..9521076c7764b 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index c9d913ce2d674..da6d31f71d833 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index dd75701b21206..c590631e4a924 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 6ecf8708cf624..1b86f7bde3d36 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index abb1641cf80fc..1f03144d8c984 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 4d720b6f3b369..6c95729e13465 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 1d9bd857ee936..f108b8912ef1f 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 442b99515a627..815fbc79381f5 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 5dd1b93e76d91..d2fb74a8df79a 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 722483ffe6b9c..b35f7fbdc582b 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 03fd05649991b..04bc3715bdf10 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 0a6719c8574a5..dce8a32cab76e 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 59e6a989d2ee7..2db61b54da6ec 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_browser.mdx b/api_docs/kbn_core_feature_flags_browser.mdx index 60508586b6c2d..bf266901489ea 100644 --- a/api_docs/kbn_core_feature_flags_browser.mdx +++ b/api_docs/kbn_core_feature_flags_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-browser title: "@kbn/core-feature-flags-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-browser'] --- import kbnCoreFeatureFlagsBrowserObj from './kbn_core_feature_flags_browser.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_browser_internal.mdx b/api_docs/kbn_core_feature_flags_browser_internal.mdx index dedd15f1a63ad..a7a1c6f7494aa 100644 --- a/api_docs/kbn_core_feature_flags_browser_internal.mdx +++ b/api_docs/kbn_core_feature_flags_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-browser-internal title: "@kbn/core-feature-flags-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-browser-internal'] --- import kbnCoreFeatureFlagsBrowserInternalObj from './kbn_core_feature_flags_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_browser_mocks.mdx b/api_docs/kbn_core_feature_flags_browser_mocks.mdx index 31cb95535092c..058e805245f3c 100644 --- a/api_docs/kbn_core_feature_flags_browser_mocks.mdx +++ b/api_docs/kbn_core_feature_flags_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-browser-mocks title: "@kbn/core-feature-flags-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-browser-mocks'] --- import kbnCoreFeatureFlagsBrowserMocksObj from './kbn_core_feature_flags_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_server.mdx b/api_docs/kbn_core_feature_flags_server.mdx index 0ac4684c2af4f..2200d1ac5541c 100644 --- a/api_docs/kbn_core_feature_flags_server.mdx +++ b/api_docs/kbn_core_feature_flags_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-server title: "@kbn/core-feature-flags-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-server'] --- import kbnCoreFeatureFlagsServerObj from './kbn_core_feature_flags_server.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_server_internal.mdx b/api_docs/kbn_core_feature_flags_server_internal.mdx index 5eab9cf75afcb..31f36391a030b 100644 --- a/api_docs/kbn_core_feature_flags_server_internal.mdx +++ b/api_docs/kbn_core_feature_flags_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-server-internal title: "@kbn/core-feature-flags-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-server-internal'] --- import kbnCoreFeatureFlagsServerInternalObj from './kbn_core_feature_flags_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_server_mocks.mdx b/api_docs/kbn_core_feature_flags_server_mocks.mdx index a659fdb3b281c..87dc3499d3e41 100644 --- a/api_docs/kbn_core_feature_flags_server_mocks.mdx +++ b/api_docs/kbn_core_feature_flags_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-server-mocks title: "@kbn/core-feature-flags-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-server-mocks'] --- import kbnCoreFeatureFlagsServerMocksObj from './kbn_core_feature_flags_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index a1cbdfd952501..4144d515c058e 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index ebe2e8773c74e..55c051e8c9b1e 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 09f06b35914ce..2ff7d6025d3f6 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 2fe5775d09a6e..2612eb14e85ba 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index be840d960a23c..5ece0d4d9f85a 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index 3e4a6f1f0133c..07a744d9d37ac 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index f7641b092fb5c..4edd4f3c32f8a 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 2666665034b2e..dc3de0f907d89 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 1a72bc0bc6683..64e3a48b03f5f 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index acced40a40625..2280fe3f2225f 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index bb43a8a658677..ece052552bedb 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index ba772275f4b73..6774166f2d3ab 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -3681,18 +3681,6 @@ "plugin": "@kbn/content-management-favorites-server", "path": "packages/content-management/favorites/favorites_server/src/favorites_routes.ts" }, - { - "plugin": "licensing", - "path": "x-pack/plugins/licensing/server/routes/info.ts" - }, - { - "plugin": "licensing", - "path": "x-pack/plugins/licensing/server/routes/feature_usage.ts" - }, - { - "plugin": "features", - "path": "x-pack/plugins/features/server/routes/index.ts" - }, { "plugin": "taskManager", "path": "x-pack/plugins/task_manager/server/routes/health.ts" @@ -3705,6 +3693,18 @@ "plugin": "taskManager", "path": "x-pack/plugins/task_manager/server/routes/metrics.ts" }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/server/routes/info.ts" + }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/server/routes/feature_usage.ts" + }, + { + "plugin": "features", + "path": "x-pack/plugins/features/server/routes/index.ts" + }, { "plugin": "customIntegrations", "path": "src/plugins/custom_integrations/server/routes/define_routes.ts" @@ -3935,7 +3935,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/rule_types.ts" + "path": "x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.ts" }, { "plugin": "alerting", @@ -5289,18 +5289,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/health.test.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/rule_types.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/rule_types.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/rule_types.test.ts" - }, { "plugin": "features", "path": "x-pack/plugins/features/server/routes/index.test.ts" @@ -5825,6 +5813,18 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.test.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.test.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts" @@ -6581,7 +6581,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/mute_all_rule.ts" + "path": "x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.ts" }, { "plugin": "alerting", @@ -6589,7 +6589,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/unmute_all_rule.ts" + "path": "x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.ts" }, { "plugin": "alerting", @@ -7795,18 +7795,6 @@ "plugin": "actions", "path": "x-pack/plugins/actions/server/routes/get_oauth_access_token.test.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/run_soon.test.ts" @@ -7819,14 +7807,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/run_soon.test.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts" @@ -8335,6 +8315,18 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.test.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.test.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.test.ts" @@ -8355,6 +8347,14 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.test.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.test.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.test.ts" @@ -17054,6 +17054,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/apply_dataview_indices.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 5427d4c868adf..87c2b4b520206 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 1b9f87043beff..64ebf184d3866 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 707592102cd97..ea1e8fe7ce951 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index c45b919fd2286..e908be457fcdd 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 1837837bd098f..cee958a8455e6 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 5f4424b556b2c..6c2539b40b247 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 606a91d70813d..20019e9a5cdd3 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 945579b6d470a..8a5909aedd39c 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 0492d1e55b0cd..4dd7f0a63bc5f 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 104810e5d32e5..143f08cfad282 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 1ab0cb7c9a318..9496cc0a31457 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 62f149be55f56..fa31a1c719e4f 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index da9197d5c61a0..f4f9284c47bf8 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index e748ed85ac681..5a3dc9c44f26c 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index b79804d75f3c0..7dfc2d6a8e340 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 69d709329b731..bdca1b4b1cf46 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index f886aa2b78af0..fd1b58ddb9dca 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index c048a427037e4..183624a9db1cd 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 6618746bac455..08f64dc3409f5 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 66e54429e667f..498d252c67f50 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 2f37169fe37bb..2f5cdeb88c004 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index db4c5cee7fb39..253b74a7938f8 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 5b2aff30bb6c0..afed6ac0a0a21 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 880cdc606b95b..b3ee2bbb8b8ed 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 9ecce4829d3fb..3f6ce321b77a6 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 9722c15e2dae2..328dfba1891f5 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index d1e7fa8e3de3e..c1608dbd85d60 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 3f08395489bc1..39d751391a14b 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 978460390d109..990527cbb4ce2 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 015f254fb195d..b59bd11ce1244 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index c6028253aa38e..f0bff735d8a6e 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index cf571b576171d..4eadfe1e575cd 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 78fd7b466a5da..b93b69e9f862b 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 83aa2670bd57a..499c6ba2f2d24 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 01cb7ad5af768..4f06d3cba40c1 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 0bf9f3e425bf1..a2937a9020309 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index aea8b859a85d2..5b0207958dea4 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index 2ecbbe01633de..635c59e6d83aa 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index 32d85fd0b704d..c7b14e2b44fb9 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 0110a19256ecb..3ea077de995c3 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index b767150c30584..ee62f99b2ac00 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 24c07869b13e9..8d84524a5eaab 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 561bf4c2e5996..1dcfc9e35d707 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index c4719b4f3c2e1..2d0a9a844182b 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 0417dc858d400..1da1db963cbfe 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index a332bbc6f7e50..739bc63c61580 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 011e992c27458..9d67cb55a28f3 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index ca249b731ff00..752e345d6e9c9 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index a6b1bd12cbd11..b75f2531b16cd 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index ad3fb86aff907..ba9c77a0c17ed 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 1b2ad11d82d47..c73c137f876c0 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index b384a0d907cea..ee06571010f09 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index dec85d42dd7b5..d18ca2163bfc4 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 5b677b952e3bf..a048f7d1e2441 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 7b74316915ac9..f646bcb7e1080 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index eeca404e63308..9fc27afdced60 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index a15836b931640..5e813af7cba33 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index c148a4c55b853..07da71d8cedcd 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 2f5220e54d76d..6f9d8f9f5edb9 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index d316a7b8ea6c6..2ad40d3c0f0fd 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 7145a2ea1490c..f3d7020e6af29 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index a0f581a1afd3e..cb96bd879a8c5 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 56622e08bc472..39adaad671232 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 992977d857803..2eb4764ad84d1 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index 26dfdd6e42c4d..d704c99dea901 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index 80c44eda65d8c..3d157b0d08ccd 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index fcc908c634819..fc3e0050b7422 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index da5a0a42f12b2..27d5f4054f9f7 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index 309eac85aed2d..43b0f0469fcfc 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index 4922a3c706d66..8682052fd346f 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index 19aa327ebd8c1..2d04d9edd917e 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 5e69e5b65bd44..2190c3ac1f464 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 6d48f8803eace..0e29f4f759103 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index f8b031b75a1e6..485fd8e957ef8 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 177c687da8cb2..6e5b7cd4ef6f8 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index eabbd12312cf9..c92258c31d9be 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index a8d495b567a35..55f531f91a3cd 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 718f4987fa86d..579d2fac8c552 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index cff501e9a68e6..db82a5870e19f 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index 0f946ce5857c1..6510557afb537 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 06471086c8059..689319965b6f4 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 820af40a5fd2c..81c8a309ae2eb 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 8f65deeef7908..14b92c51f0653 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 01780459381cd..0705a4dc2cdc0 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index e25a2a3ec1c56..96576fe8a693b 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 1299065a58ae3..6a2cbeecd007b 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 033749b2f180d..058336a26c097 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 5a12aa70a2716..c5e519cba78a1 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 92d20b7d388fb..7ca47fb110519 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index d8d98ceb01597..90e8bb9199a75 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 55f308ad477e0..cbbd5785d1c18 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 9bc3de5ad7131..0fd4ec3b98210 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index c6e81504b004b..db9ca39e4431d 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index f3431fbdd49e7..f4fc8e5ebb1a8 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index cb99772dc2415..1d93d2181bbbd 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index 231bcfe69d582..9e309bc30e0b7 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index a4d373cd334ce..ca0bfffd715da 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index 3b08ee90b8fc6..5330d77470e1d 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index d3a8fee0a7719..0eac001b9a75f 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index 89b51dbb98ae8..b896ad8a6fd6e 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index 4b8e259b50fb1..75214265b5c68 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 056a889ce5ebb..6e04c1e032ae9 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index f47a01f56ef22..c6c0de56c08dc 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 23fe73102e547..e76b26ddc6ac5 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index f505477902cab..6ff294e24ab7d 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index 8bb822b23b7c7..6747b3ab93ede 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 2b9d04fd02189..853daaebca77e 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index f2975d2a5befd..f5ec2d5fed47e 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index fb39c7f160b1e..cf399c1266325 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 83cdc6ef4a33a..0bd654ad9b9c8 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index 60727320be4c1..7f12098cc0bc6 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index fc84a78fc16a3..597e9baf1a373 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 68bd088088875..603fbe5e176c0 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 1a5cc514a326b..c9e6737df0b27 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index 6f0c90afa6285..ceb99ede6fb64 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index 1b2e1747b7374..2f5fcc47e9d00 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index e4a206a0a54b0..2d81dfd919dc9 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 8372d3d0e60c2..1178ae0b23145 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.devdocs.json b/api_docs/kbn_deeplinks_observability.devdocs.json index bb9e832ebbb02..373db769cfb5c 100644 --- a/api_docs/kbn_deeplinks_observability.devdocs.json +++ b/api_docs/kbn_deeplinks_observability.devdocs.json @@ -857,7 +857,7 @@ "label": "AppId", "description": [], "signature": [ - "\"profiling\" | \"metrics\" | \"apm\" | \"synthetics\" | \"ux\" | \"logs\" | \"slo\" | \"observabilityAIAssistant\" | \"observability-overview\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"inventory\"" + "\"profiling\" | \"metrics\" | \"apm\" | \"synthetics\" | \"ux\" | \"logs\" | \"slo\" | \"observabilityAIAssistant\" | \"observability-overview\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\"" ], "path": "packages/deeplinks/observability/deep_links.ts", "deprecated": false, @@ -1012,6 +1012,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LAST_USED_LOGS_VIEWER_APP_ID", + "type": "string", + "tags": [], + "label": "LAST_USED_LOGS_VIEWER_APP_ID", + "description": [], + "signature": [ + "\"last-used-logs-viewer\"" + ], + "path": "packages/deeplinks/observability/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-observability", "id": "def-common.ListFilterControl", @@ -1102,6 +1117,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY", + "type": "string", + "tags": [], + "label": "OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY", + "description": [], + "signature": [ + "\"obs-logs-explorer:lastUsedViewer\"" + ], + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-observability", "id": "def-common.OBSERVABILITY_LOGS_EXPLORER_APP_ID", diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index d5ebde5a3abfe..0049dc477c884 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 63 | 0 | 51 | 0 | +| 65 | 0 | 53 | 0 | ## Common diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index ddeabb77bf832..cdbad166d4644 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index e0434620ba1df..d99e07c2341c9 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index a6b4051795ecc..795870a271dcf 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index abdb9c5dc7452..6cb8f87ca4edb 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index 122bb670c2013..a569938b5f098 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 7dcde89f441c3..76ec13f663339 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index 064a038e9ff1b..aad5713b652cf 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 9200278a97e8d..7cba291805c20 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 6d71f7b68ec98..5f377ebfc42ec 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 1c3e0a6ea1744..6e5d6f46afdfc 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 1ae919b61c49e..391607ad3901c 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.devdocs.json b/api_docs/kbn_discover_utils.devdocs.json index 52022cb6da268..01901ffe8e469 100644 --- a/api_docs/kbn_discover_utils.devdocs.json +++ b/api_docs/kbn_discover_utils.devdocs.json @@ -2813,13 +2813,13 @@ "children": [ { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.RowControlProps.datatestsubj", - "type": "string", + "id": "def-common.RowControlProps.color", + "type": "CompoundType", "tags": [], - "label": "'data-test-subj'", + "label": "color", "description": [], "signature": [ - "string | undefined" + "\"text\" | \"warning\" | \"success\" | \"primary\" | \"accent\" | \"danger\" | undefined" ], "path": "packages/kbn-discover-utils/src/components/custom_control_columns/types.ts", "deprecated": false, @@ -2827,13 +2827,22 @@ }, { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.RowControlProps.color", + "id": "def-common.RowControlProps.css", "type": "CompoundType", "tags": [], - "label": "color", + "label": "css", "description": [], "signature": [ - "\"text\" | \"warning\" | \"success\" | \"primary\" | \"accent\" | \"danger\" | undefined" + "InterpolationPrimitive", + " | ", + "ArrayInterpolation", + "<", + "Theme", + "> | ", + "FunctionInterpolation", + "<", + "Theme", + ">" ], "path": "packages/kbn-discover-utils/src/components/custom_control_columns/types.ts", "deprecated": false, @@ -2841,13 +2850,13 @@ }, { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.RowControlProps.disabled", - "type": "CompoundType", + "id": "def-common.RowControlProps.datatestsubj", + "type": "string", "tags": [], - "label": "disabled", + "label": "'data-test-subj'", "description": [], "signature": [ - "boolean | undefined" + "string | undefined" ], "path": "packages/kbn-discover-utils/src/components/custom_control_columns/types.ts", "deprecated": false, @@ -2855,11 +2864,14 @@ }, { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.RowControlProps.label", - "type": "string", + "id": "def-common.RowControlProps.disabled", + "type": "CompoundType", "tags": [], - "label": "label", + "label": "disabled", "description": [], + "signature": [ + "boolean | undefined" + ], "path": "packages/kbn-discover-utils/src/components/custom_control_columns/types.ts", "deprecated": false, "trackAdoption": false @@ -2878,6 +2890,17 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.RowControlProps.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-discover-utils/src/components/custom_control_columns/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.RowControlProps.onClick", diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index dd069e7f90e7a..3b1f8ebcc823c 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 180 | 0 | 146 | 1 | +| 181 | 0 | 147 | 1 | ## Common diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 18d2d662b9017..0e610973d281d 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index d57e41bbbf82d..1731b5b0b9356 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index b8804fad20748..a22384897a070 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 55cb8fc47a66a..8af428544f07d 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index ad9db29de2d3b..4da4385b24b21 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 43a7377aef288..62d0dd6a99d44 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 6de70532e0e67..3f22e8ac32b45 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index ca9740e84e268..242d07f3f1f25 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index 066aaa8207c97..b9dfe9e6dcccf 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index e0c851e3b86a8..acbd2aecb492c 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 1424d7bdbaad2..38b2ef89d0c5c 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 2b579a5827e7b..c5b49d4c113a2 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index e564eeaf39b03..d00e32e287917 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index fda61abd0c28a..7508897fc51ac 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index eda032d5971b4..0a20e39434834 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index d2a95d129eca5..9dced3016d1a3 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; diff --git a/api_docs/kbn_esql_editor.mdx b/api_docs/kbn_esql_editor.mdx index 9ea87b228b417..f9c16dee14fbe 100644 --- a/api_docs/kbn_esql_editor.mdx +++ b/api_docs/kbn_esql_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-editor title: "@kbn/esql-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-editor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-editor'] --- import kbnEsqlEditorObj from './kbn_esql_editor.devdocs.json'; diff --git a/api_docs/kbn_esql_utils.devdocs.json b/api_docs/kbn_esql_utils.devdocs.json index 1fad7649e8ec1..e1a02ca0349f7 100644 --- a/api_docs/kbn_esql_utils.devdocs.json +++ b/api_docs/kbn_esql_utils.devdocs.json @@ -1358,6 +1358,59 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/esql-utils", + "id": "def-common.isESQLColumnGroupable", + "type": "Function", + "tags": [], + "label": "isESQLColumnGroupable", + "description": [ + "\nCheck if a column is groupable (| STATS ... BY ).\n" + ], + "signature": [ + "(column: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.DatatableColumn", + "text": "DatatableColumn" + }, + ") => boolean" + ], + "path": "packages/kbn-esql-utils/src/utils/esql_fields_utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-utils", + "id": "def-common.isESQLColumnGroupable.$1", + "type": "Object", + "tags": [], + "label": "column", + "description": [ + "The DatatableColumn of the field." + ], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.DatatableColumn", + "text": "DatatableColumn" + } + ], + "path": "packages/kbn-esql-utils/src/utils/esql_fields_utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "True if the column is groupable, false otherwise." + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/esql-utils", "id": "def-common.isESQLColumnSortable", diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index a2796497e54de..e0d1c64f80a74 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 77 | 0 | 71 | 0 | +| 79 | 0 | 71 | 0 | ## Common diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index 1241008e0b921..09c6540704d0c 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 2f7db6e62f392..4078a78381f79 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index 1ecbd84acf6b9..a930cb0189e63 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index 05e4eb6e04a00..75c86f8f193a9 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 09b629899b4b7..e94a967f587c1 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index 6059cc6854baa..4d58927c510d8 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index e860bf6ceb838..a727856b71c41 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_formatters.mdx b/api_docs/kbn_formatters.mdx index 215d36d141de0..08df835db146d 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 64a9c46dc6e29..7c219b1fbdd17 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 3d9024cf99fcb..c56b858e7d1c3 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index da9c73e722b83..4406239ceec00 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index 83a5f51e4d91f..cbe6830e60477 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 246f16e53a0bf..32462129f46c1 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grid_layout.mdx b/api_docs/kbn_grid_layout.mdx index 29314fe28dd3f..61e8a4f8430d3 100644 --- a/api_docs/kbn_grid_layout.mdx +++ b/api_docs/kbn_grid_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grid-layout title: "@kbn/grid-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grid-layout plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grid-layout'] --- import kbnGridLayoutObj from './kbn_grid_layout.devdocs.json'; diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index 1e6fde108df51..93f65182969c0 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 76ed17aaad140..05e9ee2ff5a02 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index c72b0ec0bcbd1..f7bf51367a5ab 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 7caf2d32cf3aa..f4bbb61214f45 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index b95d0f48f47bf..c5e28820fa2aa 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 569eb2ab13477..05b52749babfc 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index cdcc9d76aaf13..cadc4c5a4cab0 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 41cc44c4089a2..0ebc07e77cd42 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 3434ca22ed42a..a2afdb16e26a5 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 156f8df69be2b..5c1ee0d1bc5e5 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_management_shared_types.mdx b/api_docs/kbn_index_management_shared_types.mdx index 27a292052c3a8..52b3cad73ded7 100644 --- a/api_docs/kbn_index_management_shared_types.mdx +++ b/api_docs/kbn_index_management_shared_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management-shared-types title: "@kbn/index-management-shared-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management-shared-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management-shared-types'] --- import kbnIndexManagementSharedTypesObj from './kbn_index_management_shared_types.devdocs.json'; diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index c1ec42b1f327f..af83ebd2b00fc 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 0a6f7d82cb9a5..25f5890af0629 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 14fd213149119..c7de26de5d291 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_investigation_shared.mdx b/api_docs/kbn_investigation_shared.mdx index c20a50a551865..edf443eb97778 100644 --- a/api_docs/kbn_investigation_shared.mdx +++ b/api_docs/kbn_investigation_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-investigation-shared title: "@kbn/investigation-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/investigation-shared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/investigation-shared'] --- import kbnInvestigationSharedObj from './kbn_investigation_shared.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 2d3690bde8dc9..204edede832db 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index 0c200de12ed44..a6797e4967f86 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 1e3aa733759b1..bb37c57fe51ec 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index c83b4bca55310..e85736eba19ce 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 84b4766b38c55..84bbd714abd80 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_json_schemas.mdx b/api_docs/kbn_json_schemas.mdx index c5d2499e38f43..560627459f28d 100644 --- a/api_docs/kbn_json_schemas.mdx +++ b/api_docs/kbn_json_schemas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-schemas title: "@kbn/json-schemas" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-schemas plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-schemas'] --- import kbnJsonSchemasObj from './kbn_json_schemas.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 1aeea06f5de50..e7d187a7702f5 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation.mdx b/api_docs/kbn_language_documentation.mdx index 0eb74e9cbba9b..1003be802e282 100644 --- a/api_docs/kbn_language_documentation.mdx +++ b/api_docs/kbn_language_documentation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation title: "@kbn/language-documentation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation'] --- import kbnLanguageDocumentationObj from './kbn_language_documentation.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index f13903fc3b17c..db8e9af72957c 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index 7a2185b35e230..fadde33ce61e4 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 3b9c96e8d48d7..8649df694e003 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 36ab94064c9d1..599cc3ee0ed6d 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index 5582b8e6c5ff1..6af79cf48c3c6 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index ef68359f63801..1b265fd72daa6 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index ed735d713f338..0fb50326e8452 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index 809f3c9cbcadf..351535df22c90 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index de6fb0372dded..553a0f34fe42b 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index e13cd0ad8ca23..3a45a2e6696a9 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index aca8ed1de1881..b9c327a7e3dc5 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index e4e3c4f508893..2b856654e644c 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index fbdbde622a439..8ac312f563fee 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 9f8726354c662..0605b73743c28 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 3468b494cb2b1..d2607361bba8a 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index 0e12968b816af..e17dfa99c5397 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index cf6b3cc0d4a88..53ff3818454ef 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index 028e536d48d92..4e960bfe63a4f 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 47ba706a0282e..00fbe0540e37f 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 61f04dfa3865d..dd1443cd6b0aa 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 96a0c2395a1c5..4e8df579b5cb9 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index e5e66957d1861..c241b57245bc7 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 136947f9607f1..fb1d4ae37d968 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index 3d6663626e177..3b8e794df12a9 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index 583bafb528cdd..9dd6c9fc03c93 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index d29f4150df6c9..a68d5e3014bdb 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index 1ae92f5cb72b0..1e87bde6d003b 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 950aeab205992..291add53a1f97 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index 8cf786ca256b8..673fad40efe01 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index 0da4ced2aafdd..5a3802b00454a 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_field_stats_flyout.devdocs.json b/api_docs/kbn_ml_field_stats_flyout.devdocs.json index d286eb16a26d3..c9de1cb6cda92 100644 --- a/api_docs/kbn_ml_field_stats_flyout.devdocs.json +++ b/api_docs/kbn_ml_field_stats_flyout.devdocs.json @@ -3,59 +3,6 @@ "client": { "classes": [], "functions": [ - { - "parentPluginId": "@kbn/ml-field-stats-flyout", - "id": "def-public.EuiComboBoxWithFieldStats", - "type": "Function", - "tags": [ - "component" - ], - "label": "EuiComboBoxWithFieldStats", - "description": [ - "\nReact component that wraps the EuiComboBox component and adds field statistics functionality.\n" - ], - "signature": [ - "(props: ", - { - "pluginId": "@kbn/ml-field-stats-flyout", - "scope": "public", - "docId": "kibKbnMlFieldStatsFlyoutPluginApi", - "section": "def-public.EuiComboBoxWithFieldStatsProps", - "text": "EuiComboBoxWithFieldStatsProps" - }, - ") => React.JSX.Element" - ], - "path": "x-pack/packages/ml/field_stats_flyout/eui_combo_box_with_field_stats.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/ml-field-stats-flyout", - "id": "def-public.EuiComboBoxWithFieldStats.$1", - "type": "CompoundType", - "tags": [], - "label": "props", - "description": [ - "- The component props." - ], - "signature": [ - { - "pluginId": "@kbn/ml-field-stats-flyout", - "scope": "public", - "docId": "kibKbnMlFieldStatsFlyoutPluginApi", - "section": "def-public.EuiComboBoxWithFieldStatsProps", - "text": "EuiComboBoxWithFieldStatsProps" - } - ], - "path": "x-pack/packages/ml/field_stats_flyout/eui_combo_box_with_field_stats.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/ml-field-stats-flyout", "id": "def-public.FieldStatsContent", @@ -268,6 +215,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/ml-field-stats-flyout", + "id": "def-public.OptionListWithFieldStats", + "type": "Function", + "tags": [], + "label": "OptionListWithFieldStats", + "description": [], + "signature": [ + "({ options, placeholder, singleSelection, onChange, selectedOptions, fullWidth, isDisabled, isLoading, isClearable, \"aria-label\": ariaLabel, \"data-test-subj\": dataTestSubj, }: OptionListWithFieldStatsProps) => React.JSX.Element" + ], + "path": "x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ml-field-stats-flyout", + "id": "def-public.OptionListWithFieldStats.$1", + "type": "Object", + "tags": [], + "label": "{\n options,\n placeholder,\n singleSelection = false,\n onChange,\n selectedOptions,\n fullWidth,\n isDisabled,\n isLoading,\n isClearable = true,\n 'aria-label': ariaLabel,\n 'data-test-subj': dataTestSubj,\n}", + "description": [], + "signature": [ + "OptionListWithFieldStatsProps" + ], + "path": "x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/ml-field-stats-flyout", "id": "def-public.useFieldStatsFlyoutContext", @@ -299,9 +279,7 @@ "\nCustom hook for managing field statistics trigger functionality.\n" ], "signature": [ - "() => { renderOption: (option: ", - "EuiComboBoxOptionOption", - ", searchValue: string) => React.ReactNode; setIsFlyoutVisible: (v: boolean) => void; setFieldName: (v: string | undefined) => void; handleFieldStatsButtonClick: (field: ", + "() => { renderOption: (option: T) => React.ReactNode; setIsFlyoutVisible: (v: boolean) => void; setFieldName: (v: string | undefined) => void; handleFieldStatsButtonClick: (field: ", { "pluginId": "@kbn/ml-field-stats-flyout", "scope": "public", @@ -581,19 +559,45 @@ "misc": [ { "parentPluginId": "@kbn/ml-field-stats-flyout", - "id": "def-public.EuiComboBoxWithFieldStatsProps", + "id": "def-public.DropDownLabel", "type": "Type", "tags": [], - "label": "EuiComboBoxWithFieldStatsProps", - "description": [ - "\nProps for the EuiComboBoxWithFieldStats component." - ], + "label": "DropDownLabel", + "description": [], "signature": [ - "Omit<", - "_EuiComboBoxProps", - ", \"options\" | \"selectedOptions\" | \"optionMatcher\" | \"async\" | \"compressed\" | \"fullWidth\" | \"isClearable\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\"> & Partial>" - ], - "path": "x-pack/packages/ml/field_stats_flyout/eui_combo_box_with_field_stats.tsx", + "(", + "EuiComboBoxOptionOption", + " & BaseOption<", + { + "pluginId": "@kbn/ml-anomaly-utils", + "scope": "common", + "docId": "kibKbnMlAnomalyUtilsPluginApi", + "section": "def-common.Aggregation", + "text": "Aggregation" + }, + ">) | (", + "DisambiguateSet", + "<", + "EuiSelectableGroupLabelOption", + ">, ", + "EuiSelectableLIOption", + ">> & ", + "CommonProps", + " & { label: string; searchableLabel?: string | undefined; key?: string | undefined; checked?: \"mixed\" | \"on\" | \"off\" | undefined; disabled?: boolean | undefined; isGroupLabel?: false | undefined; prepend?: React.ReactNode; append?: React.ReactNode; ref?: ((optionIndex: number) => void) | undefined; id?: undefined; data?: { [key: string]: any; } | undefined; textWrap?: \"wrap\" | \"truncate\" | undefined; truncationProps?: Partial> | undefined; toolTipContent?: React.ReactNode; toolTipProps?: Partial> | undefined; } & React.HTMLAttributes & BaseOption) | (", + "DisambiguateSet", + "<", + "EuiSelectableLIOption", + ">, ", + "EuiSelectableGroupLabelOption", + ">> & Omit<", + "EuiSelectableOptionBase", + ", \"isGroupLabel\"> & React.HTMLAttributes & { isGroupLabel: true; } & BaseOption)" + ], + "path": "x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_ml_field_stats_flyout.mdx b/api_docs/kbn_ml_field_stats_flyout.mdx index ac39e1ff34b77..bf1c2f161fe4d 100644 --- a/api_docs/kbn_ml_field_stats_flyout.mdx +++ b/api_docs/kbn_ml_field_stats_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-field-stats-flyout title: "@kbn/ml-field-stats-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-field-stats-flyout plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-field-stats-flyout'] --- import kbnMlFieldStatsFlyoutObj from './kbn_ml_field_stats_flyout.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 29 | 0 | 0 | 0 | +| 29 | 0 | 3 | 0 | ## Client diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index 24606191b2380..474c6a5309e3c 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 5e7ad9716ea64..b4792a12f2b82 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 27b4091b3700f..5ac741d98a6c9 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index 25d09004581b3..8cfaf88659a59 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index bef9319af4a3e..073e958337f66 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 88dab017349c8..ba6eb727fc5af 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 70472e29dfd8e..288a1b885239a 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_parse_interval.mdx b/api_docs/kbn_ml_parse_interval.mdx index 993904b8ff06c..e904dc099d4ec 100644 --- a/api_docs/kbn_ml_parse_interval.mdx +++ b/api_docs/kbn_ml_parse_interval.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-parse-interval title: "@kbn/ml-parse-interval" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-parse-interval plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-parse-interval'] --- import kbnMlParseIntervalObj from './kbn_ml_parse_interval.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 677698429bfd6..09341f9fa5e7a 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 1d1d58e9ccb88..812fb5eed54e0 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 99ba9827aa2b7..af560ceab2c88 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index 4b20cb12bb3f9..e68bb5cdb0233 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 42244b2831fcf..e61d8b4fec6e1 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index c10d94ad6ddd9..d25c59323b75c 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 0d154784afe03..a95d916b8f16a 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 4ff745426e3a2..cabb8445768b4 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 1572f973a68c1..5d0eb1bd69485 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_ml_validators.mdx b/api_docs/kbn_ml_validators.mdx index b5e406ea97406..e746ebd66e36a 100644 --- a/api_docs/kbn_ml_validators.mdx +++ b/api_docs/kbn_ml_validators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-validators title: "@kbn/ml-validators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-validators plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-validators'] --- import kbnMlValidatorsObj from './kbn_ml_validators.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index 2bfbdf4d4f973..28e5e786dc12f 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index bf4125cd3f546..a8f03a850921a 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 44f84eb152ac8..639793fd7eb76 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_object_versioning_utils.mdx b/api_docs/kbn_object_versioning_utils.mdx index c4f59ff844c03..416bd49d5025c 100644 --- a/api_docs/kbn_object_versioning_utils.mdx +++ b/api_docs/kbn_object_versioning_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning-utils title: "@kbn/object-versioning-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning-utils'] --- import kbnObjectVersioningUtilsObj from './kbn_object_versioning_utils.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index 8921ecd1e074c..6cb78637ae084 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_rule_utils.mdx b/api_docs/kbn_observability_alerting_rule_utils.mdx index bb5c550ba219e..8dd3e1c70c99d 100644 --- a/api_docs/kbn_observability_alerting_rule_utils.mdx +++ b/api_docs/kbn_observability_alerting_rule_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-rule-utils title: "@kbn/observability-alerting-rule-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-rule-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-rule-utils'] --- import kbnObservabilityAlertingRuleUtilsObj from './kbn_observability_alerting_rule_utils.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index ed8001ed0c3fa..ebaee0faaecd5 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 9ca1b09c4a321..c5012de87d72a 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_observability_logs_overview.mdx b/api_docs/kbn_observability_logs_overview.mdx index 06907235c1918..37f1197431457 100644 --- a/api_docs/kbn_observability_logs_overview.mdx +++ b/api_docs/kbn_observability_logs_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-logs-overview title: "@kbn/observability-logs-overview" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-logs-overview plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-logs-overview'] --- import kbnObservabilityLogsOverviewObj from './kbn_observability_logs_overview.devdocs.json'; diff --git a/api_docs/kbn_observability_synthetics_test_data.mdx b/api_docs/kbn_observability_synthetics_test_data.mdx index 08ee964c44296..4477698c0220b 100644 --- a/api_docs/kbn_observability_synthetics_test_data.mdx +++ b/api_docs/kbn_observability_synthetics_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-synthetics-test-data title: "@kbn/observability-synthetics-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-synthetics-test-data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-synthetics-test-data'] --- import kbnObservabilitySyntheticsTestDataObj from './kbn_observability_synthetics_test_data.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index b47ae5a791f19..03d85b45e228f 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index d9d29f66aff78..1a44740d3d11a 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index cc9ef2a0bf332..50fe2aaad254f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 0d2ac49760201..3b72459aa6352 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 72f74b12ac361..773ee97b810c7 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index e7e0d9fa9442d..bd99a2e1e2c91 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 11839d5e98f10..521014aca1dad 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index 8a312c6712ccf..33bedc3d0b91d 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index e1908987cc2ac..e24591eb87847 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index b39d5236abb26..036cdea571cba 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 4ccc45a3f8087..a78531aa1cc50 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index 02f381e984b00..23003ad9e3854 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; diff --git a/api_docs/kbn_product_doc_artifact_builder.mdx b/api_docs/kbn_product_doc_artifact_builder.mdx index 1c904617a42f9..876f8511889fd 100644 --- a/api_docs/kbn_product_doc_artifact_builder.mdx +++ b/api_docs/kbn_product_doc_artifact_builder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-product-doc-artifact-builder title: "@kbn/product-doc-artifact-builder" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/product-doc-artifact-builder plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/product-doc-artifact-builder'] --- import kbnProductDocArtifactBuilderObj from './kbn_product_doc_artifact_builder.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index ef91e8957a7d3..655500b8cf884 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index d7b759abf9a62..c1bd3ee9a494c 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 5f5748488ca58..5fefe8d589ba1 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index ce4ffa94463d0..cf338427fb482 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index ee2ace3512742..f6aa1cf906924 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index 3b7e2d76d8f27..8e08d4155e452 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index c754de221223d..09d48f6b2ff6b 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index 2642a3a92addc..3f24c21604f3d 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index 85e11ab253c8e..0cc277bad6d23 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index 131929894b7d2..9da520d2bff9f 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_recently_accessed.mdx b/api_docs/kbn_recently_accessed.mdx index dca7732b91d6f..c6ba90fd78e14 100644 --- a/api_docs/kbn_recently_accessed.mdx +++ b/api_docs/kbn_recently_accessed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-recently-accessed title: "@kbn/recently-accessed" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/recently-accessed plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/recently-accessed'] --- import kbnRecentlyAccessedObj from './kbn_recently_accessed.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 6a09b4e47851c..bdcea707a0fdb 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index ec88f0dde404c..564e5e35f1306 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index 35d5f63036cde..80562ffa7389e 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 95250078fa0ac..e8a63f928acb5 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index 87e22b22548df..388cfff419625 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_csv_share_panel.mdx b/api_docs/kbn_reporting_csv_share_panel.mdx index 89eea7be0238e..4ecdb783db5c0 100644 --- a/api_docs/kbn_reporting_csv_share_panel.mdx +++ b/api_docs/kbn_reporting_csv_share_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-csv-share-panel title: "@kbn/reporting-csv-share-panel" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-csv-share-panel plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-csv-share-panel'] --- import kbnReportingCsvSharePanelObj from './kbn_reporting_csv_share_panel.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index 524cf2d5ca245..9ac0beab5f769 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index 8d46123ebab54..aeec9f7c8ee7a 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index cd7d787058a82..3179c764114e5 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 5da0165a1f1ea..1c33c2eb0bcbf 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index f2b39dbec4d8a..4ec49a0d1dccc 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index 79264d1808aaa..02a7f0efaa848 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index 237592cc68ba1..42030debb655a 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index d90581f2cb4f5..4946f323d47c8 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index 1bc9d5a3b5821..1de72b2180756 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 4b93851792863..488b4e19ff5e2 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx index f3e58e3dd8628..814b86910ab45 100644 --- a/api_docs/kbn_response_ops_feature_flag_service.mdx +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service title: "@kbn/response-ops-feature-flag-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-feature-flag-service plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] --- import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 4808bb447ffb4..87eb149336ee3 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rollup.mdx b/api_docs/kbn_rollup.mdx index 6354fad20b5ae..13139e3c961c7 100644 --- a/api_docs/kbn_rollup.mdx +++ b/api_docs/kbn_rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rollup title: "@kbn/rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rollup plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rollup'] --- import kbnRollupObj from './kbn_rollup.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index c2ec1b174aead..c70a4ba53e77c 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index 27b3b9f0d8b1c..e18190d9e4196 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 46bbc7339d5d7..bdb51d507b020 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 9a791805d926e..01607209156b1 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -671,6 +671,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_RULE_EXECUTION_TYPE", + "type": "string", + "tags": [], + "label": "ALERT_RULE_EXECUTION_TYPE", + "description": [], + "signature": [ + "\"kibana.alert.rule.execution.type\"" + ], + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ALERT_RULE_EXECUTION_UUID", @@ -1579,7 +1594,7 @@ "label": "DefaultAlertFieldName", "description": [], "signature": [ - "\"@timestamp\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.revision\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.status\" | \"kibana.alert.uuid\" | \"kibana.space_ids\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.consecutive_matches\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.flapping_history\" | \"kibana.alert.intended_timestamp\" | \"kibana.alert.last_detected\" | \"kibana.alert.maintenance_window_ids\" | \"kibana.alert.previous_action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.execution.timestamp\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.tags\" | \"kibana.alert.severity_improving\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.url\" | \"kibana.alert.workflow_assignee_ids\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_tags\" | \"kibana.version\" | \"kibana.alert\" | \"kibana.alert.rule\"" + "\"@timestamp\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.revision\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.status\" | \"kibana.alert.uuid\" | \"kibana.space_ids\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.consecutive_matches\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.flapping_history\" | \"kibana.alert.intended_timestamp\" | \"kibana.alert.last_detected\" | \"kibana.alert.maintenance_window_ids\" | \"kibana.alert.previous_action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.execution.timestamp\" | \"kibana.alert.rule.execution.type\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.tags\" | \"kibana.alert.severity_improving\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.url\" | \"kibana.alert.workflow_assignee_ids\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_tags\" | \"kibana.version\" | \"kibana.alert\" | \"kibana.alert.rule\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 1b675340a5d9c..15ee1d2ea3f93 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detections-response](https://github.com/orgs/elastic/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 129 | 0 | 126 | 0 | +| 130 | 0 | 127 | 0 | ## Common diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 3b5f63877942a..72b5e98975c64 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_screenshotting_server.mdx b/api_docs/kbn_screenshotting_server.mdx index a7a171fed4061..d2000340e1a57 100644 --- a/api_docs/kbn_screenshotting_server.mdx +++ b/api_docs/kbn_screenshotting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-screenshotting-server title: "@kbn/screenshotting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/screenshotting-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/screenshotting-server'] --- import kbnScreenshottingServerObj from './kbn_screenshotting_server.devdocs.json'; diff --git a/api_docs/kbn_search_api_keys_components.mdx b/api_docs/kbn_search_api_keys_components.mdx index ab33db485db8d..8b44659a8cd9b 100644 --- a/api_docs/kbn_search_api_keys_components.mdx +++ b/api_docs/kbn_search_api_keys_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-keys-components title: "@kbn/search-api-keys-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-keys-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-keys-components'] --- import kbnSearchApiKeysComponentsObj from './kbn_search_api_keys_components.devdocs.json'; diff --git a/api_docs/kbn_search_api_keys_server.mdx b/api_docs/kbn_search_api_keys_server.mdx index 7289b6e8e51cd..006b262374199 100644 --- a/api_docs/kbn_search_api_keys_server.mdx +++ b/api_docs/kbn_search_api_keys_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-keys-server title: "@kbn/search-api-keys-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-keys-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-keys-server'] --- import kbnSearchApiKeysServerObj from './kbn_search_api_keys_server.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index 0c7934b257c9e..622ece1570d38 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index b218eabe6412b..44d259f5cbd22 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 4382e59d70e1d..df7c073a5ad6d 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index b1ee5c83b727c..f5b259eb58417 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 672bd0ae4457a..1f8bea7ae9c58 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_shared_ui.mdx b/api_docs/kbn_search_shared_ui.mdx index e1cc2d3a2284d..aea465da0e2b3 100644 --- a/api_docs/kbn_search_shared_ui.mdx +++ b/api_docs/kbn_search_shared_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-shared-ui title: "@kbn/search-shared-ui" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-shared-ui plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-shared-ui'] --- import kbnSearchSharedUiObj from './kbn_search_shared_ui.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index 01775170ce67f..6348ab0bab9dc 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_api_key_management.mdx b/api_docs/kbn_security_api_key_management.mdx index e0ee7086d6725..535dbf3b60108 100644 --- a/api_docs/kbn_security_api_key_management.mdx +++ b/api_docs/kbn_security_api_key_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-api-key-management title: "@kbn/security-api-key-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-api-key-management plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-api-key-management'] --- import kbnSecurityApiKeyManagementObj from './kbn_security_api_key_management.devdocs.json'; diff --git a/api_docs/kbn_security_authorization_core.devdocs.json b/api_docs/kbn_security_authorization_core.devdocs.json index 75a43d937d9f0..a46a78d6cbc0e 100644 --- a/api_docs/kbn_security_authorization_core.devdocs.json +++ b/api_docs/kbn_security_authorization_core.devdocs.json @@ -170,6 +170,82 @@ } ], "functions": [ + { + "parentPluginId": "@kbn/security-authorization-core", + "id": "def-server.getReplacedByForPrivilege", + "type": "Function", + "tags": [], + "label": "getReplacedByForPrivilege", + "description": [ + "\nReturns a list of privileges that replace the given privilege, if any. Works for both top-level\nand sub-feature privileges." + ], + "signature": [ + "(privilegeId: string, privilege: ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivileges", + "text": "FeatureKibanaPrivileges" + }, + ") => readonly ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivilegesReference", + "text": "FeatureKibanaPrivilegesReference" + }, + "[] | undefined" + ], + "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/security-authorization-core", + "id": "def-server.getReplacedByForPrivilege.$1", + "type": "string", + "tags": [], + "label": "privilegeId", + "description": [ + "The ID of the privilege to get replacements for." + ], + "signature": [ + "string" + ], + "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/security-authorization-core", + "id": "def-server.getReplacedByForPrivilege.$2", + "type": "Object", + "tags": [], + "label": "privilege", + "description": [ + "The privilege definition to get replacements for." + ], + "signature": [ + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivileges", + "text": "FeatureKibanaPrivileges" + } + ], + "path": "x-pack/packages/security/authorization_core/src/privileges/privileges.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/security-authorization-core", "id": "def-server.privilegesFactory", @@ -300,10 +376,10 @@ "signature": [ "(respectLicenseLevel?: boolean | undefined) => ", { - "pluginId": "@kbn/security-authorization-core", - "scope": "server", - "docId": "kibKbnSecurityAuthorizationCorePluginApi", - "section": "def-server.RawKibanaPrivileges", + "pluginId": "@kbn/security-plugin-types-common", + "scope": "common", + "docId": "kibKbnSecurityPluginTypesCommonPluginApi", + "section": "def-common.RawKibanaPrivileges", "text": "RawKibanaPrivileges" } ], @@ -331,110 +407,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaFeaturePrivileges", - "type": "Interface", - "tags": [], - "label": "RawKibanaFeaturePrivileges", - "description": [], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaFeaturePrivileges.Unnamed", - "type": "IndexSignature", - "tags": [], - "label": "[featureId: string]: { [privilegeId: string]: string[]; }", - "description": [], - "signature": [ - "[featureId: string]: { [privilegeId: string]: string[]; }" - ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaPrivileges", - "type": "Interface", - "tags": [], - "label": "RawKibanaPrivileges", - "description": [], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaPrivileges.global", - "type": "Object", - "tags": [], - "label": "global", - "description": [], - "signature": [ - "{ [x: string]: string[]; }" - ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaPrivileges.features", - "type": "Object", - "tags": [], - "label": "features", - "description": [], - "signature": [ - { - "pluginId": "@kbn/security-authorization-core", - "scope": "server", - "docId": "kibKbnSecurityAuthorizationCorePluginApi", - "section": "def-server.RawKibanaFeaturePrivileges", - "text": "RawKibanaFeaturePrivileges" - } - ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaPrivileges.space", - "type": "Object", - "tags": [], - "label": "space", - "description": [], - "signature": [ - "{ [x: string]: string[]; }" - ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/security-authorization-core", - "id": "def-server.RawKibanaPrivileges.reserved", - "type": "Object", - "tags": [], - "label": "reserved", - "description": [], - "signature": [ - "{ [x: string]: string[]; }" - ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [], diff --git a/api_docs/kbn_security_authorization_core.mdx b/api_docs/kbn_security_authorization_core.mdx index 3208341b8209f..70dca68870748 100644 --- a/api_docs/kbn_security_authorization_core.mdx +++ b/api_docs/kbn_security_authorization_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-authorization-core title: "@kbn/security-authorization-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-authorization-core plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-authorization-core'] --- import kbnSecurityAuthorizationCoreObj from './kbn_security_authorization_core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 25 | 0 | 24 | 7 | +| 21 | 0 | 17 | 7 | ## Server diff --git a/api_docs/kbn_security_authorization_core_common.devdocs.json b/api_docs/kbn_security_authorization_core_common.devdocs.json new file mode 100644 index 0000000000000..592f265dd0b9e --- /dev/null +++ b/api_docs/kbn_security_authorization_core_common.devdocs.json @@ -0,0 +1,102 @@ +{ + "id": "@kbn/security-authorization-core-common", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/security-authorization-core-common", + "id": "def-common.getMinimalPrivilegeId", + "type": "Function", + "tags": [], + "label": "getMinimalPrivilegeId", + "description": [ + "\nReturns the minimal privilege ID for the given privilege ID." + ], + "signature": [ + "(privilegeId: string) => string" + ], + "path": "x-pack/packages/security/authorization_core_common/src/privileges/minimal_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/security-authorization-core-common", + "id": "def-common.getMinimalPrivilegeId.$1", + "type": "string", + "tags": [], + "label": "privilegeId", + "description": [ + "The privilege ID to get the minimal privilege ID for. Only `all` and `read`\nprivileges have \"minimal\" equivalents." + ], + "signature": [ + "string" + ], + "path": "x-pack/packages/security/authorization_core_common/src/privileges/minimal_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-authorization-core-common", + "id": "def-common.isMinimalPrivilegeId", + "type": "Function", + "tags": [], + "label": "isMinimalPrivilegeId", + "description": [ + "\nMinimal privileges only exist for top-level privileges, as \"minimal\" means a privilege without\nany associated sub-feature privileges. Currently, sub-feature privileges cannot include or be\nassociated with other sub-feature privileges. We use \"minimal\" privileges under the hood when\nadmins customize sub-feature privileges for a given top-level privilege. We have only\n`minimal_all` and `minimal_read` minimal privileges.\n\nFor example, let’s assume we have a feature Alpha with `All` and `Read` top-level privileges, and\n`Sub-alpha-1` and `Sub-alpha-2` sub-feature privileges, which are **by default included** in the\n`All` top-level privilege. When an admin toggles the `All` privilege for feature Alpha and\ndoesn’t change anything else, the resulting role will only have the `feature-alpha.all`\nprivilege, which assumes/includes both `sub-alpha-1` and `sub-alpha-2`. However, if the admin\ndecides to customize sub-feature privileges and toggles off `Sub-alpha-2`, the resulting role\nwill include `feature-alpha.minimal_all` and `feature-alpha.sub-alpha-1` thus excluding\n`feature-alpha.sub-alpha-2` that's included in `feature-alpha.all`, but not in\n`feature-alpha.minimal_all`.\n\nReturns true if the given privilege ID is a minimal feature privilege." + ], + "signature": [ + "(privilegeId: string) => boolean" + ], + "path": "x-pack/packages/security/authorization_core_common/src/privileges/minimal_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/security-authorization-core-common", + "id": "def-common.isMinimalPrivilegeId.$1", + "type": "string", + "tags": [], + "label": "privilegeId", + "description": [ + "The privilege ID to check." + ], + "signature": [ + "string" + ], + "path": "x-pack/packages/security/authorization_core_common/src/privileges/minimal_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_security_authorization_core_common.mdx b/api_docs/kbn_security_authorization_core_common.mdx new file mode 100644 index 0000000000000..728a0678a14bc --- /dev/null +++ b/api_docs/kbn_security_authorization_core_common.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSecurityAuthorizationCoreCommonPluginApi +slug: /kibana-dev-docs/api/kbn-security-authorization-core-common +title: "@kbn/security-authorization-core-common" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/security-authorization-core-common plugin +date: 2024-10-15 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-authorization-core-common'] +--- +import kbnSecurityAuthorizationCoreCommonObj from './kbn_security_authorization_core_common.devdocs.json'; + + + +Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 4 | 0 | 0 | 0 | + +## Common + +### Functions + + diff --git a/api_docs/kbn_security_form_components.mdx b/api_docs/kbn_security_form_components.mdx index 3ffc0096fb42c..0c2a9ac080bfb 100644 --- a/api_docs/kbn_security_form_components.mdx +++ b/api_docs/kbn_security_form_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-form-components title: "@kbn/security-form-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-form-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-form-components'] --- import kbnSecurityFormComponentsObj from './kbn_security_form_components.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index dce6f670acbc8..ef63b0e7ed215 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.devdocs.json b/api_docs/kbn_security_plugin_types_common.devdocs.json index c19a34122d799..66423093f7f10 100644 --- a/api_docs/kbn_security_plugin_types_common.devdocs.json +++ b/api_docs/kbn_security_plugin_types_common.devdocs.json @@ -596,6 +596,110 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaFeaturePrivileges", + "type": "Interface", + "tags": [], + "label": "RawKibanaFeaturePrivileges", + "description": [], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaFeaturePrivileges.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[featureId: string]: { [privilegeId: string]: string[]; }", + "description": [], + "signature": [ + "[featureId: string]: { [privilegeId: string]: string[]; }" + ], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaPrivileges", + "type": "Interface", + "tags": [], + "label": "RawKibanaPrivileges", + "description": [], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaPrivileges.global", + "type": "Object", + "tags": [], + "label": "global", + "description": [], + "signature": [ + "{ [x: string]: string[]; }" + ], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaPrivileges.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "signature": [ + { + "pluginId": "@kbn/security-plugin-types-common", + "scope": "common", + "docId": "kibKbnSecurityPluginTypesCommonPluginApi", + "section": "def-common.RawKibanaFeaturePrivileges", + "text": "RawKibanaFeaturePrivileges" + } + ], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaPrivileges.space", + "type": "Object", + "tags": [], + "label": "space", + "description": [], + "signature": [ + "{ [x: string]: string[]; }" + ], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.RawKibanaPrivileges.reserved", + "type": "Object", + "tags": [], + "label": "reserved", + "description": [], + "signature": [ + "{ [x: string]: string[]; }" + ], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/security-plugin-types-common", "id": "def-common.RestApiKey", diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 0f356132471cf..8110d94f47cf5 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 118 | 0 | 59 | 0 | +| 125 | 0 | 66 | 0 | ## Common diff --git a/api_docs/kbn_security_plugin_types_public.devdocs.json b/api_docs/kbn_security_plugin_types_public.devdocs.json index e325665dd9def..202ea68ed3592 100644 --- a/api_docs/kbn_security_plugin_types_public.devdocs.json +++ b/api_docs/kbn_security_plugin_types_public.devdocs.json @@ -31,10 +31,10 @@ }, ") => Promise<", { - "pluginId": "@kbn/security-authorization-core", - "scope": "server", - "docId": "kibKbnSecurityAuthorizationCorePluginApi", - "section": "def-server.RawKibanaPrivileges", + "pluginId": "@kbn/security-plugin-types-common", + "scope": "common", + "docId": "kibKbnSecurityPluginTypesCommonPluginApi", + "section": "def-common.RawKibanaPrivileges", "text": "RawKibanaPrivileges" }, ">" diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 5b11555b32f87..e13c32a571dcf 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index e3473a933471a..0cfc91a91b8ab 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_role_management_model.devdocs.json b/api_docs/kbn_security_role_management_model.devdocs.json index 7182dd6e7bd9c..48d4990029549 100644 --- a/api_docs/kbn_security_role_management_model.devdocs.json +++ b/api_docs/kbn_security_role_management_model.devdocs.json @@ -170,10 +170,10 @@ "description": [], "signature": [ { - "pluginId": "@kbn/security-authorization-core", - "scope": "server", - "docId": "kibKbnSecurityAuthorizationCorePluginApi", - "section": "def-server.RawKibanaPrivileges", + "pluginId": "@kbn/security-plugin-types-common", + "scope": "common", + "docId": "kibKbnSecurityPluginTypesCommonPluginApi", + "section": "def-common.RawKibanaPrivileges", "text": "RawKibanaPrivileges" } ], @@ -477,22 +477,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "@kbn/security-role-management-model", - "id": "def-common.PrimaryFeaturePrivilege.isMinimalFeaturePrivilege", - "type": "Function", - "tags": [], - "label": "isMinimalFeaturePrivilege", - "description": [], - "signature": [ - "() => boolean" - ], - "path": "x-pack/packages/security/role_management_model/src/primary_feature_privilege.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, { "parentPluginId": "@kbn/security-role-management-model", "id": "def-common.PrimaryFeaturePrivilege.getMinimalPrivilegeId", diff --git a/api_docs/kbn_security_role_management_model.mdx b/api_docs/kbn_security_role_management_model.mdx index 89a3e513267d4..4155e33ec01e1 100644 --- a/api_docs/kbn_security_role_management_model.mdx +++ b/api_docs/kbn_security_role_management_model.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-role-management-model title: "@kbn/security-role-management-model" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-role-management-model plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-role-management-model'] --- import kbnSecurityRoleManagementModelObj from './kbn_security_role_management_model.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 75 | 0 | 74 | 0 | +| 74 | 0 | 73 | 0 | ## Common diff --git a/api_docs/kbn_security_solution_common.mdx b/api_docs/kbn_security_solution_common.mdx index 71236ad1482a7..3d67063aeb3c7 100644 --- a/api_docs/kbn_security_solution_common.mdx +++ b/api_docs/kbn_security_solution_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-common title: "@kbn/security-solution-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-common plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-common'] --- import kbnSecuritySolutionCommonObj from './kbn_security_solution_common.devdocs.json'; diff --git a/api_docs/kbn_security_solution_distribution_bar.mdx b/api_docs/kbn_security_solution_distribution_bar.mdx index a0c098bb05725..78988c9fe976f 100644 --- a/api_docs/kbn_security_solution_distribution_bar.mdx +++ b/api_docs/kbn_security_solution_distribution_bar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-distribution-bar title: "@kbn/security-solution-distribution-bar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-distribution-bar plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-distribution-bar'] --- import kbnSecuritySolutionDistributionBarObj from './kbn_security_solution_distribution_bar.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.devdocs.json b/api_docs/kbn_security_solution_features.devdocs.json index 09c15deddd86b..032915d7f4cbc 100644 --- a/api_docs/kbn_security_solution_features.devdocs.json +++ b/api_docs/kbn_security_solution_features.devdocs.json @@ -65,7 +65,7 @@ "section": "def-common.AppCategory", "text": "AppCategory" }, - "; management?: { [sectionId: string]: readonly string[]; } | undefined; app: readonly string[]; privileges: { all: ", + "; management?: { [sectionId: string]: readonly string[]; } | undefined; app: readonly string[]; readonly deprecated?: Readonly<{ notice: string; }> | undefined; privileges: { all: ", { "pluginId": "features", "scope": "common", @@ -178,7 +178,7 @@ "section": "def-common.AppCategory", "text": "AppCategory" }, - "; management?: { [sectionId: string]: readonly string[]; } | undefined; app: readonly string[]; privileges: { all: ", + "; management?: { [sectionId: string]: readonly string[]; } | undefined; app: readonly string[]; readonly deprecated?: Readonly<{ notice: string; }> | undefined; privileges: { all: ", { "pluginId": "features", "scope": "common", @@ -445,7 +445,23 @@ "section": "def-common.RecursivePartial", "text": "RecursivePartial" }, - "<\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined>; alerting?: ", + "<\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined>; replacedBy?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "; alerting?: ", { "pluginId": "@kbn/utility-types", "scope": "common", diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index 6f5ce668a1075..74fd558c9238e 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 7ecd9cadb08d3..e161d2a17a909 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 051b615f554dd..ca167cfe4c06e 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index 3c074f4719df4..e7267a1dff213 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_security_ui_components.mdx b/api_docs/kbn_security_ui_components.mdx index 171bee086bf78..9729cdce6c83c 100644 --- a/api_docs/kbn_security_ui_components.mdx +++ b/api_docs/kbn_security_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-ui-components title: "@kbn/security-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-ui-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-ui-components'] --- import kbnSecurityUiComponentsObj from './kbn_security_ui_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 13c5061bd4b5e..7ffab65d18d25 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index 162f62f98d3c5..ac77314c087cc 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 1e95742baef79..0dcdd24a109c8 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 7f6583c58309a..2d6df046d3b95 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index fbb423ff05373..d9a27d4064fcd 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index cb71c04784788..2f86047dcf393 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 7d3fe53a908e6..7384c23943dfb 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 23fcf4086ceed..54db9f450f23b 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 0ec92251df0e0..3e6823bf5bd27 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index b4a41faf35ead..3cbce872df7c0 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 39ec3bcd742bc..75a0138202e71 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 13bce27075e11..6bc164bc5f847 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index a757cb72bb2ca..2711832ccce54 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 0aac9c52b3c66..b74df171e9fcf 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index a5ee9fe9ce533..f3b64da8a4a92 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 65c5bb6d48fd8..56890a675d09e 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index e20a6e9f63af5..80f791615aa6b 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 860fcf80fd07c..c3e3076fe225a 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 44da10e73f53a..f1e159d74d564 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository_client.mdx b/api_docs/kbn_server_route_repository_client.mdx index 58c435297ced9..557938ae7f201 100644 --- a/api_docs/kbn_server_route_repository_client.mdx +++ b/api_docs/kbn_server_route_repository_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository-client title: "@kbn/server-route-repository-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository-client plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository-client'] --- import kbnServerRouteRepositoryClientObj from './kbn_server_route_repository_client.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository_utils.mdx b/api_docs/kbn_server_route_repository_utils.mdx index 0746547fd3820..7665ace22e707 100644 --- a/api_docs/kbn_server_route_repository_utils.mdx +++ b/api_docs/kbn_server_route_repository_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository-utils title: "@kbn/server-route-repository-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository-utils'] --- import kbnServerRouteRepositoryUtilsObj from './kbn_server_route_repository_utils.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index b573bb0949eb4..ef523175f9e12 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index ac57a947a839a..927f6e78bcbf3 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index 3219e52a68776..fb765744984d4 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index 29f5fb43d18e9..8bd2c5aa8e33f 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 7e0790d38cebf..de091926d3422 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 467021b720da5..ef2c5763bd0b0 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 6331535f3be6a..02292a5a98fc3 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index bd74cbe898195..af5b57e852c1d 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index a4036f35b1b79..293275ce9c1b2 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 85b6437283585..611fccfd29d0d 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 0ad3db353c2f9..0b61142e6da64 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index c0ea17327c221..f6cf8c87fbfc9 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json b/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json index 4ff7fa24b4255..11d4e3caff928 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json +++ b/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json @@ -318,17 +318,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/shared-ux-chrome-navigation", - "id": "def-public.NavigationServices.navIsOpen", - "type": "boolean", - "tags": [], - "label": "navIsOpen", - "description": [], - "path": "packages/shared-ux/chrome/navigation/src/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/shared-ux-chrome-navigation", "id": "def-public.NavigationServices.navigateToUrl", diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index abfb5e9402acc..defa81af3f81f 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 39 | 0 | 30 | 2 | +| 38 | 0 | 29 | 2 | ## Client diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index a5d089bb56db1..5f14acbb6c102 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 58008bc1f3093..302cc192f73d5 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 92a070929806a..cbeaceb9af242 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index f1382c99b473c..3f9d4def56c12 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index c426cb27639d2..d27a3e2fbfb4e 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 0cbfe60a05684..968a5d75ec4c9 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 55079532bcf66..92225baa6c76b 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index e9abf51e906c7..4f49f970e64e2 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 723a1f9eceb71..f13ea537e6f9a 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index c4f7bd8754ab1..a7c89d3018f66 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 905327ca83de9..5e24564d7dd4d 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index d6d62560a3b9b..567a97944aa0a 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index dc549660dd6f2..282c53b8db0d1 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 18622f06bee0a..6daa811183df1 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index f4f61f07f16ea..929f130498e32 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 71f2b7168a67a..268212b30a1c6 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 51d198389c636..6c826c13b7370 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 50b89a0338bb9..a80d48170ce70 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index ea3259b742f64..a05235e754861 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 09e746b3c68a1..f6698a068e847 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index bcbe09cc6741f..18ecdc2a31d36 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index f7e335bc21ad1..44cb09d7187e0 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 0e83154bf02fe..a73dfb2000840 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 2905b2c341977..2e3973e833a9c 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index a32cefc162279..eb2cdf0f151c2 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 5295121232c7b..2ebcca7066cda 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index ce0875d7e156d..76d0af87270bd 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index af6b312301f24..659402311aa13 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 09199e595814c..e0ffba43ebf58 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 9bae18e6c47b6..e990dae52b81d 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index a389d0c6f8121..70cd8c9f1f6e4 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index c0eb2eaacef69..a5b2d18e8f7da 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_table_persist.mdx b/api_docs/kbn_shared_ux_table_persist.mdx index 78c87c1597f0e..3102164376447 100644 --- a/api_docs/kbn_shared_ux_table_persist.mdx +++ b/api_docs/kbn_shared_ux_table_persist.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-table-persist title: "@kbn/shared-ux-table-persist" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-table-persist plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-table-persist'] --- import kbnSharedUxTablePersistObj from './kbn_shared_ux_table_persist.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 98456d3d5f803..d8ee4d3162ef7 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 3514ced2f8f2e..e6fbba91b3e5e 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index cf08dcb52303d..e0e9808bec5b3 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 5e91d78994b27..56bb30ded685e 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_sse_utils.mdx b/api_docs/kbn_sse_utils.mdx index 6d324fa0fdef6..dec0a6ec8aba1 100644 --- a/api_docs/kbn_sse_utils.mdx +++ b/api_docs/kbn_sse_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sse-utils title: "@kbn/sse-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sse-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sse-utils'] --- import kbnSseUtilsObj from './kbn_sse_utils.devdocs.json'; diff --git a/api_docs/kbn_sse_utils_client.mdx b/api_docs/kbn_sse_utils_client.mdx index 2172e546def8b..54de31f6d2c91 100644 --- a/api_docs/kbn_sse_utils_client.mdx +++ b/api_docs/kbn_sse_utils_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sse-utils-client title: "@kbn/sse-utils-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sse-utils-client plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sse-utils-client'] --- import kbnSseUtilsClientObj from './kbn_sse_utils_client.devdocs.json'; diff --git a/api_docs/kbn_sse_utils_server.mdx b/api_docs/kbn_sse_utils_server.mdx index 0f8892632a223..e874a588c5586 100644 --- a/api_docs/kbn_sse_utils_server.mdx +++ b/api_docs/kbn_sse_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sse-utils-server title: "@kbn/sse-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sse-utils-server plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sse-utils-server'] --- import kbnSseUtilsServerObj from './kbn_sse_utils_server.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 63ac79dc00b61..c76ea7a1d700b 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 1d502350ebb9f..34a5ca0be6d4b 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index ac669752614aa..250fb31e6533f 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_synthetics_e2e.mdx b/api_docs/kbn_synthetics_e2e.mdx index a423503874b58..67779584a2882 100644 --- a/api_docs/kbn_synthetics_e2e.mdx +++ b/api_docs/kbn_synthetics_e2e.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-synthetics-e2e title: "@kbn/synthetics-e2e" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/synthetics-e2e plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/synthetics-e2e'] --- import kbnSyntheticsE2eObj from './kbn_synthetics_e2e.devdocs.json'; diff --git a/api_docs/kbn_synthetics_private_location.mdx b/api_docs/kbn_synthetics_private_location.mdx index bf3751817e891..cd58a94da1750 100644 --- a/api_docs/kbn_synthetics_private_location.mdx +++ b/api_docs/kbn_synthetics_private_location.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-synthetics-private-location title: "@kbn/synthetics-private-location" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/synthetics-private-location plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/synthetics-private-location'] --- import kbnSyntheticsPrivateLocationObj from './kbn_synthetics_private_location.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 02f7f2ce7fe26..76f09f6d66df2 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 1d9af2fd6a030..f9abf489f7e99 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index 0a5f47f7b1a04..d4ffd4b8d43cf 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index ab4f36b1f3645..9c04a184c6224 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 17fb37bc6d231..1fcc666367c2f 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index adf825978f56d..9dd6d58086a13 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 9d2f8d78fe70f..44145f54e735b 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index 50ae29b2b1dbc..e5bb084035c18 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index c7db72532629c..dd8713752cebf 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 9f017f9eec7f0..ad10c67cbbc3b 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index d5e08b3f5b84c..9a3f164e92f55 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index af97e389baf3e..4dded9d741071 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 34b470f0d1bf0..6f78fd5d9ba73 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 3bd067b1ea18f..27c0a1a62d457 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index 115d169236f82..301866d7752c3 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 5e819f9ed66a0..07e18d21282cd 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index c4208b5f4bfe2..f6891e409fd67 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 21306dda5b1c0..22e65a439fe5a 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index 572f0430d7cf4..a1081833cb7d2 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 1361bcab4542c..ff2cd2c445231 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 8ac7ec846a9f1..b5b530b05e482 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 6a0d108af9434..e356d380e302a 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index eef976d8f0390..e277e9f80117e 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 1cf34f878982f..8cf1820ce64c9 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index 0417d4835c63f..fe9176cbb86ab 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index b5afc9cc3ea80..5dfa0b300b979 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index 70e8d880e7f20..79d880010e5d1 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index c0c89c0e636b4..e1804abac082c 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod.mdx b/api_docs/kbn_zod.mdx index 15c16ffdd214a..762eb0b57db9a 100644 --- a/api_docs/kbn_zod.mdx +++ b/api_docs/kbn_zod.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod title: "@kbn/zod" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod'] --- import kbnZodObj from './kbn_zod.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 74aab9a39bea6..5c8cd224c63c0 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 76810444ced68..cd4ea40356357 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index e5692b6705153..105c6106cabd9 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 5bfd5d03fc457..2be43b2408b35 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 79dd93ea320d9..d9df16c3ed69c 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 37a1d344440e3..fec200edeeb11 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -6210,7 +6210,7 @@ "label": "longMessage", "description": [], "signature": [ - "React.ReactNode | ((closePopover: () => void) => React.ReactNode)" + "React.ReactNode | ((closePopover?: (() => void) | undefined) => React.ReactNode)" ], "path": "x-pack/plugins/lens/public/types.ts", "deprecated": false, diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index d377297d1d249..b51013dd802e2 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index dbdc832eb9cd4..631df0f9cbd6f 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 5e4f1aec04984..7e62afe460a02 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index f06d2a8c7f3e4..69c0ee7e4a476 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index 998a1d3036a34..5954e86dbc79f 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index f010aa029ab21..06c4598f291e5 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index 8085237b50fdd..d874d2bf26696 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 01db1ff4c65ca..a45cc4597e78b 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index 5ea630a517dce..5603daceed720 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 4d0042c1c03fb..84572d8342cf2 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 4a5d91d714414..94a9a08a38197 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 3b7ae55dbe25e..5c661b00b798e 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index 2dd6839974fa6..8326202f01a52 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 0e334abd6f55d..5b8af7d10060f 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index e1470912a3b2c..4839a2e9aa821 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 33f74a56f8753..699c6fa6482f9 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index e347ee7f202fa..955d1e97dde0b 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 21e0105471c3a..39e03b4a5ce26 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 35eaebd34816a..4c957be8fc079 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index 7ed45e6df751f..c8d4461e050ed 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index cf9ac844fcb32..d838ff68cd3b0 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 14ad014989023..5c875c4abbd9a 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -3862,7 +3862,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -3889,7 +3889,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -4179,7 +4179,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & OutputOf> & TAdditionalMetaFields" + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & OutputOf> & TAdditionalMetaFields" ], "path": "x-pack/plugins/observability_solution/observability/public/typings/alerts.ts", "deprecated": false, @@ -4800,7 +4800,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -4827,7 +4827,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index b6beba7ef7f68..1ab4c70866c1b 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 10e160b411540..08f4154d52287 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index e10b500a0bf66..d36b4a55f5a42 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index 04e57ef5ef7b2..f87fe4b16c342 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index 7c9910a5f8172..5628d7fbc2060 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index bba9f268236a3..f4a3a2b1d4b9d 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.devdocs.json b/api_docs/observability_shared.devdocs.json index bb3580c4af1f7..4047b0886ada3 100644 --- a/api_docs/observability_shared.devdocs.json +++ b/api_docs/observability_shared.devdocs.json @@ -285,6 +285,53 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanel", + "type": "Function", + "tags": [], + "label": "AddDataPanel", + "description": [], + "signature": [ + "({\n content,\n actions,\n onDissmiss,\n onLearnMore,\n onTryIt,\n onAddData,\n 'data-test-subj': dataTestSubj,\n}: ", + { + "pluginId": "observabilityShared", + "scope": "public", + "docId": "kibObservabilitySharedPluginApi", + "section": "def-public.AddDataPanelProps", + "text": "AddDataPanelProps" + }, + ") => React.JSX.Element" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanel.$1", + "type": "Object", + "tags": [], + "label": "{\n content,\n actions,\n onDissmiss,\n onLearnMore,\n onTryIt,\n onAddData,\n 'data-test-subj': dataTestSubj,\n}", + "description": [], + "signature": [ + { + "pluginId": "observabilityShared", + "scope": "public", + "docId": "kibObservabilitySharedPluginApi", + "section": "def-public.AddDataPanelProps", + "text": "AddDataPanelProps" + } + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "observabilityShared", "id": "def-public.allCasesPermissions", @@ -2307,6 +2354,126 @@ } ], "interfaces": [ + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps", + "type": "Interface", + "tags": [], + "label": "AddDataPanelProps", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.content", + "type": "Object", + "tags": [], + "label": "content", + "description": [], + "signature": [ + "AddDataPanelContent" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.onDissmiss", + "type": "Function", + "tags": [], + "label": "onDissmiss", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.onAddData", + "type": "Function", + "tags": [], + "label": "onAddData", + "description": [], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.onTryIt", + "type": "Function", + "tags": [], + "label": "onTryIt", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.onLearnMore", + "type": "Function", + "tags": [], + "label": "onLearnMore", + "description": [], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.actions", + "type": "Object", + "tags": [], + "label": "actions", + "description": [], + "signature": [ + "{ primary: Required; secondary?: AddDataPanelButton | undefined; link: AddDataPanelButton; }" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.AddDataPanelProps.datatestsubj", + "type": "string", + "tags": [], + "label": "'data-test-subj'", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_shared/public/components/add_data_panel/index.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "observabilityShared", "id": "def-public.ApmIndicesConfig", @@ -6269,6 +6436,30 @@ } ], "enums": [ + { + "parentPluginId": "observabilityShared", + "id": "def-common.EntityDataStreamType", + "type": "Enum", + "tags": [], + "label": "EntityDataStreamType", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_shared/common/entity/entity_data_stream_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "observabilityShared", + "id": "def-common.EntityType", + "type": "Enum", + "tags": [], + "label": "EntityType", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_shared/common/entity/entity_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observabilityShared", "id": "def-common.IndexLifecyclePhaseSelectOption", diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 718f1d84ad966..f7496195b222c 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 493 | 1 | 488 | 19 | +| 505 | 1 | 500 | 19 | ## Client diff --git a/api_docs/osquery.devdocs.json b/api_docs/osquery.devdocs.json index dc574c1d8c7d1..942a222062531 100644 --- a/api_docs/osquery.devdocs.json +++ b/api_docs/osquery.devdocs.json @@ -309,7 +309,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & { _index: string; }) | undefined) => Promise<{ response: { action_id: string; '@timestamp': string; expiration: string; type: string; input_type: string; alert_ids: string[] | undefined; event_ids: string[] | undefined; case_ids: string[] | undefined; agent_ids: string[] | undefined; agent_all: boolean | undefined; agent_platforms: string[] | undefined; agent_policy_ids: string[] | undefined; agents: string[]; user_id: string | undefined; metadata: object | undefined; pack_id: string | undefined; pack_name: string | undefined; pack_prebuilt: boolean | undefined; queries: _.Dictionary[]; }; fleetActionsCount: number; }>; stop: () => void; }" + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & { _index: string; }) | undefined) => Promise<{ response: { action_id: string; '@timestamp': string; expiration: string; type: string; input_type: string; alert_ids: string[] | undefined; event_ids: string[] | undefined; case_ids: string[] | undefined; agent_ids: string[] | undefined; agent_all: boolean | undefined; agent_platforms: string[] | undefined; agent_policy_ids: string[] | undefined; agents: string[]; user_id: string | undefined; metadata: object | undefined; pack_id: string | undefined; pack_name: string | undefined; pack_prebuilt: boolean | undefined; queries: _.Dictionary[]; }; fleetActionsCount: number; }>; stop: () => void; }" ], "path": "x-pack/plugins/osquery/server/types.ts", "deprecated": false, diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index d3a337dd28237..b08ea2060cb40 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index 046046d1e7644..3cdf959b91221 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index f33ac9f1a5523..06ccc6f87dbb1 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 872 | 744 | 45 | +| 873 | 745 | 45 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 53751 | 242 | 40369 | 1999 | +| 53819 | 242 | 40421 | 2002 | ## Plugin Directory @@ -97,9 +97,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart. | 6 | 0 | 6 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart. | 180 | 0 | 169 | 13 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Adds expression runtime to Kibana | 2235 | 17 | 1765 | 6 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 255 | 0 | 105 | 2 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 270 | 0 | 110 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Index pattern fields and ambiguous values formatters | 292 | 5 | 253 | 3 | -| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes services for async usage and search of fields metadata. | 42 | 0 | 42 | 7 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes services for async usage and search of fields metadata. | 44 | 0 | 44 | 9 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 88 | 0 | 88 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 3 | 0 | 3 | 0 | @@ -158,7 +158,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 2 | 0 | 2 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin exposes and registers observability log consumption features. | 19 | 0 | 19 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 24 | 0 | 24 | 0 | -| | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 493 | 1 | 488 | 19 | +| | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 505 | 1 | 500 | 19 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 23 | 0 | 23 | 7 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds a standardized Presentation panel which allows any forward ref component to interface with various Kibana systems. | 11 | 0 | 11 | 4 | @@ -186,7 +186,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 10 | 0 | 10 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 21 | 0 | 15 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | -| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 448 | 0 | 231 | 0 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 450 | 0 | 233 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 185 | 0 | 117 | 32 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Serverless customizations for security. | 7 | 0 | 7 | 0 | @@ -210,7 +210,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 226 | 1 | 182 | 17 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [@elastic/kibana-localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 592 | 1 | 566 | 51 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 593 | 1 | 567 | 51 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 156 | 0 | 110 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 212 | 0 | 145 | 11 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer). | 15 | 0 | 10 | 3 | @@ -259,8 +259,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 1 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 18 | 0 | 18 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 4 | 0 | 4 | 0 | -| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 64 | 0 | 64 | 10 | -| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 223 | 0 | 223 | 36 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 72 | 0 | 72 | 11 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 240 | 0 | 240 | 36 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 317 | 0 | 316 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 11 | 0 | 11 | 0 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 3 | 0 | 3 | 0 | @@ -490,7 +490,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 3 | 0 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 63 | 0 | 51 | 0 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 65 | 0 | 53 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 17 | 0 | 17 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 5 | 0 | 5 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | @@ -502,7 +502,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 102 | 0 | 86 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 15 | 0 | 9 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 38 | 2 | 33 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 180 | 0 | 146 | 1 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 181 | 0 | 147 | 1 | | | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 79 | 0 | 79 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 5 | 0 | 5 | 1 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 57 | 0 | 30 | 6 | @@ -520,7 +520,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 266 | 1 | 208 | 34 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 29 | 0 | 12 | 0 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 77 | 0 | 71 | 0 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 79 | 0 | 71 | 0 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 202 | 0 | 190 | 12 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 40 | 0 | 40 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | @@ -588,7 +588,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 50 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 11 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 36 | 4 | 8 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 29 | 0 | 0 | 0 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 29 | 0 | 3 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 12 | 0 | 1 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 2 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 2 | 0 | @@ -663,7 +663,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 10 | 0 | 10 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 2 | 0 | 1 | 1 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 16 | 0 | 16 | 1 | -| | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 129 | 0 | 126 | 0 | +| | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 130 | 0 | 127 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 35 | 0 | 34 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 8 | 0 | 8 | 1 | @@ -676,13 +676,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 50 | 0 | 25 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 66 | 0 | 63 | 0 | -| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 25 | 0 | 24 | 7 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 21 | 0 | 17 | 7 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 4 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 35 | 0 | 25 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 7 | 0 | 7 | 0 | -| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 118 | 0 | 59 | 0 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 125 | 0 | 66 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 66 | 0 | 39 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 275 | 1 | 154 | 0 | -| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 75 | 0 | 74 | 0 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 74 | 0 | 73 | 0 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 59 | 0 | 38 | 5 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | - | 7 | 0 | 0 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 15 | 0 | 15 | 7 | @@ -723,7 +724,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 30 | 0 | 8 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 28 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 39 | 0 | 30 | 2 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 38 | 0 | 29 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 2 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 5 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 2 | 0 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index 4626fb512ba45..71fade82e7efe 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index bd9556769c16f..2595393201b8b 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 3d306012e506e..1c3d4cec4946d 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index ea3c08726ecf4..b670dacef083a 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 9e4b66707c5c4..fc4222ce5a6b8 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index d12eb1345f02f..c06290b1a8101 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 605eda6ef5952..84c069dcf37df 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 61bae2aa71eda..d12d867f04393 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -107,7 +107,7 @@ "label": "get", "description": [], "signature": [ - "({ id, index }: GetAlertParams) => Promise<{ _index: string; \"@timestamp\"?: string | undefined; \"kibana.alert.rule.rule_type_id\"?: string | undefined; \"kibana.alert.rule.consumer\"?: string | undefined; \"kibana.alert.instance.id\"?: string | undefined; \"kibana.alert.rule.category\"?: string | undefined; \"kibana.alert.rule.name\"?: string | undefined; \"kibana.alert.rule.producer\"?: string | undefined; \"kibana.alert.rule.revision\"?: number | undefined; \"kibana.alert.rule.uuid\"?: string | undefined; \"kibana.alert.status\"?: string | undefined; \"kibana.alert.uuid\"?: string | undefined; \"kibana.space_ids\"?: string[] | undefined; \"event.action\"?: string | undefined; tags?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string | undefined; \"event.kind\"?: string | undefined; \"event.original\"?: string | undefined; \"kibana.alert.action_group\"?: string | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.consecutive_matches\"?: number | undefined; \"kibana.alert.duration.us\"?: number | undefined; \"kibana.alert.end\"?: string | undefined; \"kibana.alert.flapping\"?: boolean | undefined; \"kibana.alert.flapping_history\"?: boolean[] | undefined; \"kibana.alert.intended_timestamp\"?: string | undefined; \"kibana.alert.last_detected\"?: string | undefined; \"kibana.alert.maintenance_window_ids\"?: string[] | undefined; \"kibana.alert.previous_action_group\"?: string | undefined; \"kibana.alert.reason\"?: string | undefined; \"kibana.alert.rule.execution.timestamp\"?: string | undefined; \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.severity_improving\"?: boolean | undefined; \"kibana.alert.start\"?: string | undefined; \"kibana.alert.time_range\"?: unknown; \"kibana.alert.url\"?: string | undefined; \"kibana.alert.workflow_assignee_ids\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string | undefined; \"kibana.alert.workflow_tags\"?: string[] | undefined; \"kibana.version\"?: string | undefined; \"ecs.version\"?: string | undefined; \"kibana.alert.risk_score\"?: number | undefined; \"kibana.alert.rule.author\"?: string | undefined; \"kibana.alert.rule.created_at\"?: string | undefined; \"kibana.alert.rule.created_by\"?: string | undefined; \"kibana.alert.rule.description\"?: string | undefined; \"kibana.alert.rule.enabled\"?: string | undefined; \"kibana.alert.rule.from\"?: string | undefined; \"kibana.alert.rule.interval\"?: string | undefined; \"kibana.alert.rule.license\"?: string | undefined; \"kibana.alert.rule.note\"?: string | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string | undefined; \"kibana.alert.rule.rule_name_override\"?: string | undefined; \"kibana.alert.rule.to\"?: string | undefined; \"kibana.alert.rule.type\"?: string | undefined; \"kibana.alert.rule.updated_at\"?: string | undefined; \"kibana.alert.rule.updated_by\"?: string | undefined; \"kibana.alert.rule.version\"?: string | undefined; \"kibana.alert.severity\"?: string | undefined; \"kibana.alert.suppression.docs_count\"?: number | undefined; \"kibana.alert.suppression.end\"?: string | undefined; \"kibana.alert.suppression.start\"?: string | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.system_status\"?: string | undefined; \"kibana.alert.workflow_reason\"?: string | undefined; \"kibana.alert.workflow_status_updated_at\"?: string | undefined; \"kibana.alert.workflow_user\"?: string | undefined; }>" + "({ id, index }: GetAlertParams) => Promise<{ _index: string; \"@timestamp\"?: string | undefined; \"kibana.alert.rule.rule_type_id\"?: string | undefined; \"kibana.alert.rule.consumer\"?: string | undefined; \"kibana.alert.instance.id\"?: string | undefined; \"kibana.alert.rule.category\"?: string | undefined; \"kibana.alert.rule.name\"?: string | undefined; \"kibana.alert.rule.producer\"?: string | undefined; \"kibana.alert.rule.revision\"?: number | undefined; \"kibana.alert.rule.uuid\"?: string | undefined; \"kibana.alert.status\"?: string | undefined; \"kibana.alert.uuid\"?: string | undefined; \"kibana.space_ids\"?: string[] | undefined; \"event.action\"?: string | undefined; tags?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string | undefined; \"event.kind\"?: string | undefined; \"event.original\"?: string | undefined; \"kibana.alert.action_group\"?: string | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.consecutive_matches\"?: number | undefined; \"kibana.alert.duration.us\"?: number | undefined; \"kibana.alert.end\"?: string | undefined; \"kibana.alert.flapping\"?: boolean | undefined; \"kibana.alert.flapping_history\"?: boolean[] | undefined; \"kibana.alert.intended_timestamp\"?: string | undefined; \"kibana.alert.last_detected\"?: string | undefined; \"kibana.alert.maintenance_window_ids\"?: string[] | undefined; \"kibana.alert.previous_action_group\"?: string | undefined; \"kibana.alert.reason\"?: string | undefined; \"kibana.alert.rule.execution.timestamp\"?: string | undefined; \"kibana.alert.rule.execution.type\"?: string | undefined; \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.severity_improving\"?: boolean | undefined; \"kibana.alert.start\"?: string | undefined; \"kibana.alert.time_range\"?: unknown; \"kibana.alert.url\"?: string | undefined; \"kibana.alert.workflow_assignee_ids\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string | undefined; \"kibana.alert.workflow_tags\"?: string[] | undefined; \"kibana.version\"?: string | undefined; \"ecs.version\"?: string | undefined; \"kibana.alert.risk_score\"?: number | undefined; \"kibana.alert.rule.author\"?: string | undefined; \"kibana.alert.rule.created_at\"?: string | undefined; \"kibana.alert.rule.created_by\"?: string | undefined; \"kibana.alert.rule.description\"?: string | undefined; \"kibana.alert.rule.enabled\"?: string | undefined; \"kibana.alert.rule.from\"?: string | undefined; \"kibana.alert.rule.interval\"?: string | undefined; \"kibana.alert.rule.license\"?: string | undefined; \"kibana.alert.rule.note\"?: string | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string | undefined; \"kibana.alert.rule.rule_name_override\"?: string | undefined; \"kibana.alert.rule.to\"?: string | undefined; \"kibana.alert.rule.type\"?: string | undefined; \"kibana.alert.rule.updated_at\"?: string | undefined; \"kibana.alert.rule.updated_by\"?: string | undefined; \"kibana.alert.rule.version\"?: string | undefined; \"kibana.alert.severity\"?: string | undefined; \"kibana.alert.suppression.docs_count\"?: number | undefined; \"kibana.alert.suppression.end\"?: string | undefined; \"kibana.alert.suppression.start\"?: string | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.system_status\"?: string | undefined; \"kibana.alert.workflow_reason\"?: string | undefined; \"kibana.alert.workflow_status_updated_at\"?: string | undefined; \"kibana.alert.workflow_user\"?: string | undefined; }>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -415,7 +415,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>>, TAggregations>>" + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>>, TAggregations>>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -603,7 +603,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>>, { groupByFields: ", + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>>, { groupByFields: ", "AggregationsMultiBucketAggregateBase", "<{ key: string; }>; }>>" ], @@ -2909,7 +2909,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & OutputOf>>>(request: TSearchRequest) => Promise<", + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & OutputOf>>>(request: TSearchRequest) => Promise<", { "pluginId": "@kbn/es-types", "scope": "common", @@ -3554,7 +3554,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & OutputOf>> | null> | null" + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>> & OutputOf>> | null> | null" ], "path": "x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts", "deprecated": false, @@ -5078,7 +5078,7 @@ "section": "def-common.MultiField", "text": "MultiField" }, - "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>>" + "[]; }; readonly \"kibana.alert.rule.execution.timestamp\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.execution.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.severity_improving\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.workflow_assignee_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }>>" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, @@ -5503,7 +5503,7 @@ "label": "ParsedTechnicalFields", "description": [], "signature": [ - "{ readonly \"@timestamp\": string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.name\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.alert.rule.revision\": number; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.uuid\": string; readonly \"kibana.space_ids\": string[]; readonly \"event.action\"?: string | undefined; readonly tags?: string[] | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"event.kind\"?: string | undefined; readonly \"event.original\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.case_ids\"?: string[] | undefined; readonly \"kibana.alert.consecutive_matches\"?: number | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.flapping\"?: boolean | undefined; readonly \"kibana.alert.flapping_history\"?: boolean[] | undefined; readonly \"kibana.alert.intended_timestamp\"?: string | undefined; readonly \"kibana.alert.last_detected\"?: string | undefined; readonly \"kibana.alert.maintenance_window_ids\"?: string[] | undefined; readonly \"kibana.alert.previous_action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.execution.timestamp\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.severity_improving\"?: boolean | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.url\"?: string | undefined; readonly \"kibana.alert.workflow_assignee_ids\"?: string[] | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_tags\"?: string[] | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.workflow_status_updated_at\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; }" + "{ readonly \"@timestamp\": string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.name\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.alert.rule.revision\": number; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.uuid\": string; readonly \"kibana.space_ids\": string[]; readonly \"event.action\"?: string | undefined; readonly tags?: string[] | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"event.kind\"?: string | undefined; readonly \"event.original\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.case_ids\"?: string[] | undefined; readonly \"kibana.alert.consecutive_matches\"?: number | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.flapping\"?: boolean | undefined; readonly \"kibana.alert.flapping_history\"?: boolean[] | undefined; readonly \"kibana.alert.intended_timestamp\"?: string | undefined; readonly \"kibana.alert.last_detected\"?: string | undefined; readonly \"kibana.alert.maintenance_window_ids\"?: string[] | undefined; readonly \"kibana.alert.previous_action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.execution.timestamp\"?: string | undefined; readonly \"kibana.alert.rule.execution.type\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.severity_improving\"?: boolean | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.url\"?: string | undefined; readonly \"kibana.alert.workflow_assignee_ids\"?: string[] | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_tags\"?: string[] | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.workflow_status_updated_at\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; }" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 8ce2bcd041498..ca635b9f535ce 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 8dfa47a2e38f7..f23f88e4540aa 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 017998a8f0fbb..801a95979620c 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index e6c9c646e2e27..c38598d953648 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 5f93ea0ea2750..7be4cc28b33b9 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index de83287a7502e..0c45e6bcd0ca9 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index a9c41adf29678..f099c0f0212f6 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 5e54cb029ce5f..46d701d041082 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index f2c504284bc2a..c30c3eef24623 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 705cfc02555de..59b093abd91fd 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_assistant.mdx b/api_docs/search_assistant.mdx index 9b1a6df2b86a9..3d113256fff77 100644 --- a/api_docs/search_assistant.mdx +++ b/api_docs/search_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchAssistant title: "searchAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the searchAssistant plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchAssistant'] --- import searchAssistantObj from './search_assistant.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index a7823b43ca849..546631d7ff5db 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_homepage.mdx b/api_docs/search_homepage.mdx index d8a62950f78fb..0c66086bfd4a9 100644 --- a/api_docs/search_homepage.mdx +++ b/api_docs/search_homepage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchHomepage title: "searchHomepage" image: https://source.unsplash.com/400x175/?github description: API docs for the searchHomepage plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchHomepage'] --- import searchHomepageObj from './search_homepage.devdocs.json'; diff --git a/api_docs/search_indices.devdocs.json b/api_docs/search_indices.devdocs.json index 47f438a6bf4de..859a4e72477ee 100644 --- a/api_docs/search_indices.devdocs.json +++ b/api_docs/search_indices.devdocs.json @@ -85,7 +85,7 @@ "label": "startAppId", "description": [], "signature": [ - "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchRelevance\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"enterpriseSearchRelevance:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchRelevance\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"enterpriseSearchRelevance:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" ], "path": "x-pack/plugins/search_indices/public/types.ts", "deprecated": false, diff --git a/api_docs/search_indices.mdx b/api_docs/search_indices.mdx index 88d1e0879bc1f..2e66a51ff49d7 100644 --- a/api_docs/search_indices.mdx +++ b/api_docs/search_indices.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchIndices title: "searchIndices" image: https://source.unsplash.com/400x175/?github description: API docs for the searchIndices plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchIndices'] --- import searchIndicesObj from './search_indices.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx index d9381c30d864c..62884131d46f4 100644 --- a/api_docs/search_inference_endpoints.mdx +++ b/api_docs/search_inference_endpoints.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchInferenceEndpoints title: "searchInferenceEndpoints" image: https://source.unsplash.com/400x175/?github description: API docs for the searchInferenceEndpoints plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] --- import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index b62aa6b431881..98ff0352c62ff 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index 17c6d7fb44ed1..9c8f6b94c2ef3 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 633ceab84e178..bf69a50b11255 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -6786,6 +6786,34 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "security", + "id": "def-common.RawKibanaFeaturePrivileges", + "type": "Interface", + "tags": [], + "label": "RawKibanaFeaturePrivileges", + "description": [], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "security", + "id": "def-common.RawKibanaFeaturePrivileges.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[featureId: string]: { [privilegeId: string]: string[]; }", + "description": [], + "signature": [ + "[featureId: string]: { [privilegeId: string]: string[]; }" + ], + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "security", "id": "def-common.RawKibanaPrivileges", @@ -6793,7 +6821,7 @@ "tags": [], "label": "RawKibanaPrivileges", "description": [], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -6807,7 +6835,7 @@ "signature": [ "{ [x: string]: string[]; }" ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", "deprecated": false, "trackAdoption": false }, @@ -6820,14 +6848,14 @@ "description": [], "signature": [ { - "pluginId": "@kbn/security-authorization-core", - "scope": "server", - "docId": "kibKbnSecurityAuthorizationCorePluginApi", - "section": "def-server.RawKibanaFeaturePrivileges", + "pluginId": "@kbn/security-plugin-types-common", + "scope": "common", + "docId": "kibKbnSecurityPluginTypesCommonPluginApi", + "section": "def-common.RawKibanaFeaturePrivileges", "text": "RawKibanaFeaturePrivileges" } ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", "deprecated": false, "trackAdoption": false }, @@ -6841,7 +6869,7 @@ "signature": [ "{ [x: string]: string[]; }" ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", "deprecated": false, "trackAdoption": false }, @@ -6855,7 +6883,7 @@ "signature": [ "{ [x: string]: string[]; }" ], - "path": "x-pack/packages/security/authorization_core/src/privileges/raw_kibana_privileges.ts", + "path": "x-pack/packages/security/plugin_types_common/src/authorization/raw_kibana_privileges.ts", "deprecated": false, "trackAdoption": false } diff --git a/api_docs/security.mdx b/api_docs/security.mdx index b711ba194dc0b..660510d309a75 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 448 | 0 | 231 | 0 | +| 450 | 0 | 233 | 0 | ## Client diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index ea493b53974db..c9b53fa77d71f 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 9af7304049f97..4af1877fda649 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index 46562052c63e5..15df9d3a741e4 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 2fecd2cb97590..527b504f2e00d 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index 70b75a4d32682..5109043571940 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index 7494dde033eb4..ad497617add4a 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index a152906284097..6b7d08f028d4a 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index d87027abd1734..3b6844d93be2a 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 2e8f88ac353ff..764c0572f88ff 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 2f815e9c446af..652c48dd1a098 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index bf378fcc9b872..5134beba19c58 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index df1df5e84d77d..e0e4e9edf007d 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 0adcf78fd3173..369acc34b2b4c 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index d658beae4fd67..29b3633c6d049 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 5af587f0d87dc..d67744b921fd2 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 8834defd10bd4..5c2f92c90b14a 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index c195522ef5509..a631f5fa855ed 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 4f4eb1d6f1236..670531e282e08 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index ad197b89cb907..f82a310005240 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index f0919e232b898..e68b9fe2efd00 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index b81e66c5e8436..f3e8c7c645fab 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 0f5def15a140d..c7a192801a18d 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -5353,6 +5353,27 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUiServices.cloud", + "type": "Object", + "tags": [], + "label": "cloud", + "description": [], + "signature": [ + { + "pluginId": "cloud", + "scope": "public", + "docId": "kibCloudPluginApi", + "section": "def-public.CloudSetup", + "text": "CloudSetup" + }, + " | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/rules_app.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.TriggersAndActionsUiServices.data", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 74f74fd8c5924..be96d3ebcd0ec 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 592 | 1 | 566 | 51 | +| 593 | 1 | 567 | 51 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 420e0fd40da3f..b19c14c18175d 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index d83c6d02385a8..9193707afa0ab 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index d316862151646..c62eeb00fb7c0 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 971bd5aad0447..b917a23435165 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 47f736ce4ab13..99997235adbc7 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index d1b22e0c0cfb7..94eed8d3fd4b1 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 487f455a38320..1d3959691f4c3 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 34406fd1f090d..de87dd32031a5 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 42a931c30d2ef..11ec57f9ebcb8 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index c777d5810f300..41418511d8333 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 5f05407ee2d7e..ee9466596bc44 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 39a1b394e1f37..67788e1848051 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index c8d879ca7e35e..98e4b942e8335 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index b6362de4451b7..905e98bcbca7d 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 752f7c45dea4b..d375f18f708f2 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index aba8173a52342..7de04592e8b01 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index b4c07f3eab9fc..a553deb078ca6 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 57366cfa90427..ce2e990e72df5 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 20f57e5d383a4..ca563c1237fa5 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 2620f508cfd84..de38db32cc8d9 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 4b11744cc9dd9..5c2789677bd67 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2024-10-14 +date: 2024-10-15 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 563910b672b6dbe4f9e7931e36ec41e674fe8eb3 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:21:03 +0200 Subject: [PATCH 03/84] Update dependency @types/lodash to ^4.17.10 (main) (#194739) --- package.json | 2 +- .../src/filters/helpers/update_filter.ts | 10 ++++----- packages/kbn-esql-editor/src/esql_editor.tsx | 11 ++++------ .../src/process_versioned_router.test.ts | 4 ++-- .../options_list_search_suggestions.ts | 12 +++++----- .../common/data_views/data_views.test.ts | 8 ++++--- .../use_debounced_value.ts | 4 ++-- .../filter_manager/phrase_filter_manager.ts | 2 +- .../public/components/controls/field.tsx | 9 ++++---- .../components/controls/time_interval.tsx | 2 +- .../public/helpers/arg_value_suggestions.ts | 2 +- .../server/lib/vis_data/helpers/get_splits.ts | 6 ++--- .../table/normalize_query.ts | 4 +--- .../components/options/metrics_axes/index.tsx | 4 ++-- .../embeddable/visualize_embeddable.tsx | 2 +- .../embeddable/visualize_embeddable.tsx | 6 ++++- .../apis/kql_telemetry/kql_telemetry.ts | 8 +++---- .../src/kibana_privilege.ts | 2 +- .../lib/sanitize_bulk_response.ts | 4 ++-- .../axis_config/extended_template.tsx | 2 +- .../partition_labels/extended_template.tsx | 8 +++---- .../partition_labels/simple_template.tsx | 2 +- .../uis/arguments/partition_labels/utils.ts | 2 +- .../series_style/extended_template.tsx | 8 +++---- .../arg_types/series_style/index.ts | 4 ++-- .../series_style/simple_template.tsx | 2 +- .../canvas/public/functions/filters.ts | 4 ++-- .../functions/plot/get_flot_axis_config.ts | 2 +- x-pack/plugins/canvas/public/lib/filter.ts | 4 ++-- .../serialize_migrate_and_allocate_actions.ts | 4 ++-- .../es_search_source/es_search_source.tsx | 1 + .../styles/vector/components/field_select.tsx | 2 +- .../properties/dynamic_style_property.tsx | 6 ++--- .../join_editor/resources/join.tsx | 2 +- .../models/results_service/anomaly_charts.ts | 4 ++-- .../public/application/hooks/use_title.ts | 2 +- .../monitoring/public/lib/setup_mode.tsx | 2 +- .../collectors/lib/fetch_license_type.test.ts | 2 +- .../collectors/lib/fetch_license_type.ts | 2 +- .../kibana_monitoring/collectors/types.ts | 2 +- .../alerts/fetch_cpu_usage_node_stats.test.ts | 4 ++-- .../lib/alerts/fetch_cpu_usage_node_stats.ts | 12 +++++----- .../fetch_missing_monitoring_data.test.ts | 4 ++-- .../alerts/fetch_missing_monitoring_data.ts | 2 +- .../server/lib/details/get_series.ts | 2 +- .../elasticsearch/shards/get_shard_stats.ts | 2 +- .../lib/metrics/classes/quota_metric.ts | 2 +- .../lib/metrics/elasticsearch/classes.ts | 7 +++--- .../server/lib/metrics/logstash/classes.ts | 4 ++-- .../server/routes/api/v1/elasticsearch/ccr.ts | 1 + .../api/v1/elasticsearch/index_detail.ts | 2 +- .../api/v1/elasticsearch/node_detail.ts | 3 ++- .../get_high_level_stats.ts | 14 +++++++----- .../fleet/register_fleet_policy_callbacks.ts | 4 ++-- .../metric_detail/components/helpers.ts | 2 +- .../helpers/log_rate_analysis_query.ts | 4 ++-- .../components/eql_query_bar/footer.tsx | 4 ++-- .../rule_exceptions/utils/helpers.tsx | 4 ++-- .../left/components/suppressed_alerts.tsx | 3 ++- .../components/correlations_overview.tsx | 3 ++- .../cypress/support/response_actions.ts | 3 +++ .../timeline/body/renderers/cti/helpers.ts | 1 + .../cti/threat_match_row_renderer.test.tsx | 2 +- .../process_tree_node/index.test.tsx | 5 ++--- .../server/rule_types/es_query/executor.ts | 2 +- .../group1/tests/alerting/backfill/api_key.ts | 2 +- .../tests/alerting/backfill/delete_rule.ts | 6 ++--- .../tests/alerting/backfill/schedule.ts | 22 ++++++++++++------- .../tests/fleet/apm_package_policy.spec.ts | 8 +++---- .../common/kql_telemetry/kql_telemetry.ts | 8 +++---- yarn.lock | 10 ++++----- 71 files changed, 167 insertions(+), 154 deletions(-) diff --git a/package.json b/package.json index 46aea7c827e72..2ccaec7dff97e 100644 --- a/package.json +++ b/package.json @@ -1587,7 +1587,7 @@ "@types/jsonwebtoken": "^9.0.0", "@types/license-checker": "15.0.0", "@types/loader-utils": "^2.0.3", - "@types/lodash": "^4.14.159", + "@types/lodash": "^4.17.10", "@types/lru-cache": "^5.1.0", "@types/lz-string": "^1.3.34", "@types/mapbox__vector-tile": "1.3.0", diff --git a/packages/kbn-es-query/src/filters/helpers/update_filter.ts b/packages/kbn-es-query/src/filters/helpers/update_filter.ts index 069c70ca19a05..05936047b960b 100644 --- a/packages/kbn-es-query/src/filters/helpers/update_filter.ts +++ b/packages/kbn-es-query/src/filters/helpers/update_filter.ts @@ -17,7 +17,7 @@ export const updateFilter = ( operator?: FilterMeta, params?: Filter['meta']['params'], fieldType?: string -) => { +): Filter => { if (!field || !operator) { return updateField(filter, field); } @@ -35,7 +35,7 @@ export const updateFilter = ( return updateWithIsOperator(filter, operator, params, fieldType); }; -function updateField(filter: Filter, field?: string) { +function updateField(filter: Filter, field?: string): Filter { return { ...filter, meta: { @@ -48,7 +48,7 @@ function updateField(filter: Filter, field?: string) { type: undefined, }, query: undefined, - }; + } as Filter; // need the casting because `field` shouldn't be there } function updateWithExistsOperator(filter: Filter, operator?: FilterMeta) { @@ -104,7 +104,7 @@ function updateWithRangeOperator( operator: FilterMeta, rawParams: Filter['meta']['params'] | undefined, field: string -) { +): Filter { if (isRangeFilterParams(rawParams)) { const { from, to } = rawParams; const params = { @@ -148,7 +148,7 @@ function updateWithRangeOperator( }, }, }; - return updatedFilter; + return updatedFilter as Filter; // need the casting because it doesn't like the types of `params.gte|lt` } } diff --git a/packages/kbn-esql-editor/src/esql_editor.tsx b/packages/kbn-esql-editor/src/esql_editor.tsx index abdaa577c4bea..97340dc20d422 100644 --- a/packages/kbn-esql-editor/src/esql_editor.tsx +++ b/packages/kbn-esql-editor/src/esql_editor.tsx @@ -321,13 +321,10 @@ export const ESQLEditor = memo(function ESQLEditor({ }, []); const { cache: dataSourcesCache, memoizedSources } = useMemo(() => { - const fn = memoize( - (...args: [DataViewsPublicPluginStart, CoreStart]) => ({ - timestamp: Date.now(), - result: getESQLSources(...args), - }), - ({ esql }) => esql - ); + const fn = memoize((...args: [DataViewsPublicPluginStart, CoreStart]) => ({ + timestamp: Date.now(), + result: getESQLSources(...args), + })); return { cache: fn.cache, memoizedSources: fn }; }, []); diff --git a/packages/kbn-router-to-openapispec/src/process_versioned_router.test.ts b/packages/kbn-router-to-openapispec/src/process_versioned_router.test.ts index 9addfdf22da01..6452c2cf3c2cc 100644 --- a/packages/kbn-router-to-openapispec/src/process_versioned_router.test.ts +++ b/packages/kbn-router-to-openapispec/src/process_versioned_router.test.ts @@ -131,7 +131,7 @@ describe('processVersionedRouter', () => { {} ); - expect(Object.keys(get(baseCase, 'paths["/foo"].get.responses.200.content'))).toEqual([ + expect(Object.keys(get(baseCase, 'paths["/foo"].get.responses.200.content')!)).toEqual([ 'application/test+json; Elastic-Api-Version=2023-10-31', 'application/test+json; Elastic-Api-Version=2024-12-31', ]); @@ -142,7 +142,7 @@ describe('processVersionedRouter', () => { createOperationIdCounter(), { version: '2023-10-31' } ); - expect(Object.keys(get(filteredCase, 'paths["/foo"].get.responses.200.content'))).toEqual([ + expect(Object.keys(get(filteredCase, 'paths["/foo"].get.responses.200.content')!)).toEqual([ 'application/test+json; Elastic-Api-Version=2023-10-31', ]); }); diff --git a/src/plugins/controls/server/options_list/suggestion_queries/options_list_search_suggestions.ts b/src/plugins/controls/server/options_list/suggestion_queries/options_list_search_suggestions.ts index 7bd20eb350dde..aa41ef1575c69 100644 --- a/src/plugins/controls/server/options_list/suggestion_queries/options_list_search_suggestions.ts +++ b/src/plugins/controls/server/options_list/suggestion_queries/options_list_search_suggestions.ts @@ -117,13 +117,11 @@ const suggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregationBu const isNested = fieldSpec && getFieldSubtypeNested(fieldSpec); basePath += isNested ? '.nestedSuggestions.filteredSuggestions' : '.filteredSuggestions'; - const suggestions = get(rawEsResult, `${basePath}.suggestions.buckets`)?.reduce( - (acc: OptionsListSuggestions, suggestion: EsBucket) => { - acc.push({ value: suggestion.key, docCount: suggestion.doc_count }); - return acc; - }, - [] - ); + const buckets = get(rawEsResult, `${basePath}.suggestions.buckets`, []) as EsBucket[]; + const suggestions = buckets.reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => { + acc.push({ value: suggestion.key, docCount: suggestion.doc_count }); + return acc; + }, []); return { suggestions, totalCardinality: get(rawEsResult, `${basePath}.unique_terms.value`), diff --git a/src/plugins/data_views/common/data_views/data_views.test.ts b/src/plugins/data_views/common/data_views/data_views.test.ts index 8d9e2fdbc148b..917419a8457bf 100644 --- a/src/plugins/data_views/common/data_views/data_views.test.ts +++ b/src/plugins/data_views/common/data_views/data_views.test.ts @@ -8,7 +8,7 @@ */ import { set } from '@kbn/safer-lodash-set'; -import { defaults, get } from 'lodash'; +import { defaults } from 'lodash'; import { DataViewsService, DataView, DataViewLazy } from '.'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; @@ -297,7 +297,8 @@ describe('IndexPatterns', () => { test('does cache ad-hoc data views', async () => { const id = '1'; - const createFromSpecOriginal = get(indexPatterns, 'createFromSpec'); + // eslint-disable-next-line dot-notation + const createFromSpecOriginal = indexPatterns['createFromSpec']; let mockedCreateFromSpec: jest.Mock; set( @@ -340,7 +341,8 @@ describe('IndexPatterns', () => { test('does cache ad-hoc data views for DataViewLazy', async () => { const id = '1'; - const createFromSpecOriginal = get(indexPatterns, 'createFromSpecLazy'); + // eslint-disable-next-line dot-notation + const createFromSpecOriginal = indexPatterns['createFromSpecLazy']; let mockedCreateFromSpec: jest.Mock; set( diff --git a/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts index 83a6673412756..60dbc9f1fe092 100644 --- a/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts +++ b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts @@ -8,7 +8,7 @@ */ import { debounce } from 'lodash'; -import type { Cancelable } from 'lodash'; +import type { DebouncedFunc } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; import useUpdateEffect from 'react-use/lib/useUpdateEffect'; @@ -22,7 +22,7 @@ export function useDebouncedValue(value: T, timeout?: number): [T, boolean] { }, [setStoredValue, setPending] ); - const setDebouncedValue = useMemo>( + const setDebouncedValue = useMemo>>( () => (timeout ? debounce(setValue, timeout) : setValue), [setValue, timeout] ); diff --git a/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts b/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts index 5389f03dff11f..c0f980f2badb2 100644 --- a/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts +++ b/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts @@ -82,7 +82,7 @@ export class PhraseFilterManager extends FilterManager { private getValueFromFilter(kbnFilter: Filter): any { // bool filter - multiple phrase filters if (_.has(kbnFilter, 'query.bool.should')) { - return _.get(kbnFilter, 'query.bool.should') + return (_.get(kbnFilter, 'query.bool.should') as PhraseFilter[]) .map((kbnQueryFilter: PhraseFilter) => { return this.getValueFromFilter(kbnQueryFilter); }) diff --git a/src/plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx index ac1295d9a2bb4..fcebe456bd8c5 100644 --- a/src/plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx @@ -14,7 +14,7 @@ import useMount from 'react-use/lib/useMount'; import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggParam, IAggConfig, IFieldParamType, KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; +import { IAggConfig, IFieldParamType, KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; import { DataViewField } from '@kbn/data-views-plugin/public'; import { formatListAsProse, parseCommaSeparatedList, useValidation } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; @@ -47,7 +47,7 @@ function FieldParamEditor({ : []; const onChange = (options: EuiComboBoxOptionOption[]) => { - const selectedOption: DataViewField = get(options, '0.target'); + const selectedOption: DataViewField | undefined = get(options, '0.target'); if (!(aggParam.required && !selectedOption)) { setValue(selectedOption); } @@ -158,9 +158,8 @@ function getFieldTypesString(agg: IAggConfig) { } function getFieldTypes(agg: IAggConfig) { - const param = - get(agg, 'type.params', []).find((p: AggParam) => p.name === 'field') || - ({} as IFieldParamType); + const param = (get(agg, 'type.params', []).find((p) => p.name === 'field') || + {}) as IFieldParamType; return parseCommaSeparatedList(param.filterFieldTypes || []); } diff --git a/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx b/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx index eba2111d96d58..8d21aebf998b8 100644 --- a/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx @@ -147,7 +147,7 @@ function TimeIntervalParamEditor({ const onCustomInterval = (customValue: string) => setValue(customValue.trim()); const onChange = (opts: EuiComboBoxOptionOption[]) => { - const selectedOpt: ComboBoxOption = get(opts, '0'); + const selectedOpt = get(opts, '0'); setValue(selectedOpt ? selectedOpt.key : ''); }; diff --git a/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts b/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts index 049577064f44a..94fb21b061c38 100644 --- a/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts +++ b/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts @@ -23,7 +23,7 @@ export function getArgValueSuggestions() { // index argument not provided return; } - const indexPatternTitle = get(indexPatternArg, 'value.text'); + const indexPatternTitle = get(indexPatternArg, 'value.text', ''); return (await indexPatterns.find(indexPatternTitle, 1)).find( (index) => index.title === indexPatternTitle diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts index 6f3eb40c7360f..520064c2d42b4 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts @@ -20,7 +20,7 @@ import type { Panel, Series } from '../../../../common/types'; import type { BaseMeta } from '../request_processors/types'; const getTimeSeries = (resp: TRawResponse, series: Series) => - get(resp, `aggregations.timeseries`) || get(resp, `aggregations.${series.id}.timeseries`); + get(resp, `aggregations.timeseries`) || get(resp, [`aggregations`, series.id, `timeseries`]); interface SplittedData { id: string; @@ -49,7 +49,7 @@ export async function getSplits>> { if (!meta) { - meta = get(resp, `aggregations.${series.id}.meta`); + meta = get(resp, `aggregations.${series.id}.meta`) as TMeta | undefined; } const color = new Color(series.color); @@ -81,7 +81,7 @@ export async function getSplits { - const bucket = get(resp, `aggregations.${series.id}.buckets.${filter.id}`); + const bucket = get(resp, [`aggregations`, series.id, `buckets`, filter.id!]); // using array path because the dotted string failed to resolve the types bucket.id = `${series.id}${SERIES_SEPARATOR}${filter.id}`; bucket.key = filter.id; bucket.splitByLabel = splitByLabel; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/normalize_query.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/normalize_query.ts index 44c520fc766c1..d2300ca6d05a9 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/normalize_query.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/normalize_query.ts @@ -26,9 +26,7 @@ const hasSiblingPipelineAggregation = (aggs: Record = {}) => */ export const normalizeQuery: TableRequestProcessorsFunction = () => { return () => (doc) => { - const series = get(doc, 'aggs.pivot.aggs') as Array<{ - aggs: Record; - }>; + const series = get(doc, 'aggs.pivot.aggs'); const normalizedSeries = {}; forEach(series, (value, seriesId) => { diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx index 2ec89ec45d7d7..c2cede542d979 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx @@ -109,8 +109,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) { } if (lastCustomLabels[axis.id] !== newCustomLabel && newCustomLabel !== '') { - const lastSeriesAggType = get(lastSeriesAgg, `${matchingSeries[0].id}.type`); - const lastSeriesAggField = get(lastSeriesAgg, `${matchingSeries[0].id}.field`); + const lastSeriesAggType = get(lastSeriesAgg, [matchingSeries[0].id, `type`]); // using array path vs. string because type inference was broken + const lastSeriesAggField = get(lastSeriesAgg, [matchingSeries[0].id, `field`]); const matchingSeriesAggType = get(matchingSeries, '[0]type.name', ''); const matchingSeriesAggField = get(matchingSeries, '[0]params.field.name', ''); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index e779282fa147a..52c76a426dc14 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -405,7 +405,7 @@ export const getVisualizeEmbeddableFactory: (deps: { } const currentVis = vis$.getValue(); if (!disableTriggers) { - const triggerId = get( + const triggerId: string = get( VIS_EVENT_TO_TRIGGER, event.name, VIS_EVENT_TO_TRIGGER.filter diff --git a/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx index 85166441a1634..4f6bfa344a0a3 100644 --- a/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx @@ -481,7 +481,11 @@ export class VisualizeEmbeddable return; } if (!this.input.disableTriggers) { - const triggerId = get(VIS_EVENT_TO_TRIGGER, event.name, VIS_EVENT_TO_TRIGGER.filter); + const triggerId: string = get( + VIS_EVENT_TO_TRIGGER, + event.name, + VIS_EVENT_TO_TRIGGER.filter + ); let context; if (triggerId === VIS_EVENT_TO_TRIGGER.applyFilter) { diff --git a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts index 7d3224c0306a5..5701c171b4683 100644 --- a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts +++ b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts @@ -49,8 +49,8 @@ export default function ({ getService }: FtrProviderContext) { q: 'type:kql-telemetry', }) .then((response) => { - const kqlTelemetryDoc = get(response, 'hits.hits[0]._source.kql-telemetry'); - expect(kqlTelemetryDoc.optInCount).to.be(1); + const optInCount = get(response, 'hits.hits[0]._source.kql-telemetry.optInCount'); + expect(optInCount).to.be(1); }); }); @@ -69,8 +69,8 @@ export default function ({ getService }: FtrProviderContext) { q: 'type:kql-telemetry', }) .then((response) => { - const kqlTelemetryDoc = get(response, 'hits.hits[0]._source.kql-telemetry'); - expect(kqlTelemetryDoc.optOutCount).to.be(1); + const optOutCount = get(response, 'hits.hits[0]._source.kql-telemetry.optOutCount'); + expect(optOutCount).to.be(1); }); }); diff --git a/x-pack/packages/security/role_management_model/src/kibana_privilege.ts b/x-pack/packages/security/role_management_model/src/kibana_privilege.ts index f38d60417e72b..69441cebe0c78 100644 --- a/x-pack/packages/security/role_management_model/src/kibana_privilege.ts +++ b/x-pack/packages/security/role_management_model/src/kibana_privilege.ts @@ -10,7 +10,7 @@ import _ from 'lodash'; export class KibanaPrivilege { constructor(public readonly id: string, public readonly actions: string[] = []) {} - public get name() { + public get name(): string { return _.upperFirst(this.id); } diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/sanitize_bulk_response.ts b/x-pack/plugins/alerting/server/alerts_client/lib/sanitize_bulk_response.ts index 2b6d9f6e3c2c3..0c18500c3bd5f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/lib/sanitize_bulk_response.ts +++ b/x-pack/plugins/alerting/server/alerts_client/lib/sanitize_bulk_response.ts @@ -24,10 +24,10 @@ export const sanitizeBulkErrorResponse = ( (responseToUse.items ?? []).forEach( (item: Partial>) => { for (const [_, responseItem] of Object.entries(item)) { - const reason: string = get(responseItem, 'error.reason'); + const reason = get(responseItem, 'error.reason'); const redactIndex = reason ? reason.indexOf(`Preview of field's value:`) : -1; if (redactIndex > 1) { - set(responseItem, 'error.reason', reason.substring(0, redactIndex - 1)); + set(responseItem, 'error.reason', reason!.substring(0, redactIndex - 1)); } } } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx index 41fdc5709882c..e8710bcbd1daa 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx @@ -52,7 +52,7 @@ export class ExtendedTemplate extends PureComponent { // TODO: this should be in a helper, it's the same code from container_style getArgValue = (name: string, alt: string) => { - return get(this.props.argValue, `chain.0.arguments.${name}.0`, alt); + return get(this.props.argValue, `chain.0.arguments.${name}.0`, alt) as string | undefined; }; // TODO: this should be in a helper, it's the same code from container_style diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx index 2e7007e8d2551..8884533fb7ce3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx @@ -46,9 +46,9 @@ const PERCENT_DECIMALS_FIELD = 'percentDecimals'; export const ExtendedTemplate: FunctionComponent = ({ onValueChange, argValue }) => { const showLabels = getFieldValue(argValue, SHOW_FIELD); - const showValues = getFieldValue(argValue, VALUES_FIELD); - const valueFormat = getFieldValue(argValue, VALUES_FORMAT_FIELD); - const percentDecimals = getFieldValue(argValue, PERCENT_DECIMALS_FIELD); + const showValues = getFieldValue(argValue, VALUES_FIELD) as boolean; + const valueFormat = getFieldValue(argValue, VALUES_FORMAT_FIELD) as string; + const percentDecimals = getFieldValue(argValue, PERCENT_DECIMALS_FIELD) as string; const positions: EuiSelectOption[] = [ { text: strings.getPositionDefaultLabel(), value: 'default' }, @@ -110,7 +110,7 @@ export const ExtendedTemplate: FunctionComponent = ({ onValueChange, argV diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx index 08e27fbcd0988..eb98e0a3e0a2c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx @@ -39,7 +39,7 @@ export const SimpleTemplate: FunctionComponent = ({ onValueChange, argVal [argValue, onValueChange, showValuePath] ); - const showLabels = getFieldValue(argValue, SHOW_FIELD, false); + const showLabels = getFieldValue(argValue, SHOW_FIELD, false) as boolean; return ( diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts index 5aeffe9c6961f..974e4ef20a299 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts @@ -18,7 +18,7 @@ export const getFieldValue = ( defaultValue?: unknown ) => { if (!ast) { - return null; + return undefined; } return get(ast, getFieldPath(field), defaultValue); diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx index 2ffea6c87ea1b..67322064fcc2f 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx @@ -45,7 +45,7 @@ export const ExtendedTemplate: FunctionComponent = (props) => { } = props; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); - const selectedSeries = get(chainArgs, 'label.0', ''); + const selectedSeries = get(chainArgs, 'label.0', '') as string; let name = ''; if (typeInstance) { @@ -101,7 +101,7 @@ export const ExtendedTemplate: FunctionComponent = (props) => { handleChange('lines', ev)} @@ -113,7 +113,7 @@ export const ExtendedTemplate: FunctionComponent = (props) => { handleChange('bars', ev)} @@ -125,7 +125,7 @@ export const ExtendedTemplate: FunctionComponent = (props) => { handleChange('points', ev)} diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/index.ts b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/index.ts index 140919ed29c04..d0ac9f184aeaa 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/index.ts +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/index.ts @@ -33,13 +33,13 @@ const formatLabel = (label: string, props: Props) => { const EnhancedExtendedTemplate = compose( lifecycle({ componentWillMount() { - const label = get(this.props.argValue, 'chain.0.arguments.label.0', ''); + const label = get(this.props.argValue, 'chain.0.arguments.label.0', '') as string; if (label) { this.props.setLabel(formatLabel(label, this.props)); } }, componentDidUpdate(prevProps) { - const newLabel = get(this.props.argValue, 'chain.0.arguments.label.0', ''); + const newLabel = get(this.props.argValue, 'chain.0.arguments.label.0', '') as string; if (newLabel && prevProps.label !== formatLabel(newLabel, this.props)) { this.props.setLabel(formatLabel(newLabel, this.props)); } diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx index d54e6fdb60040..7344a3f686e93 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx @@ -44,7 +44,7 @@ export const SimpleTemplate: FunctionComponent = (props) => { const { name } = typeInstance; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); - const color: string = get(chainArgs, 'color.0', ''); + const color: string = get(chainArgs, 'color.0', '') as string; const handleChange: (key: T, val: string) => void = (argName, val) => { const fn = val === '' ? del : set; diff --git a/x-pack/plugins/canvas/public/functions/filters.ts b/x-pack/plugins/canvas/public/functions/filters.ts index a37953657e157..97049f8af57c8 100644 --- a/x-pack/plugins/canvas/public/functions/filters.ts +++ b/x-pack/plugins/canvas/public/functions/filters.ts @@ -27,14 +27,14 @@ function getFiltersByGroup(allFilters: string[], groups?: string[], ungrouped = // remove all allFilters that belong to a group return allFilters.filter((filter: string) => { const ast = fromExpression(filter); - const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []); + const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []) as string[]; return expGroups.length === 0; }); } return allFilters.filter((filter: string) => { const ast = fromExpression(filter); - const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []); + const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []) as string[]; return expGroups.length > 0 && expGroups.every((expGroup) => groups.includes(expGroup)); }); } diff --git a/x-pack/plugins/canvas/public/functions/plot/get_flot_axis_config.ts b/x-pack/plugins/canvas/public/functions/plot/get_flot_axis_config.ts index 2d58e926d28c8..ddbbb62bd1872 100644 --- a/x-pack/plugins/canvas/public/functions/plot/get_flot_axis_config.ts +++ b/x-pack/plugins/canvas/public/functions/plot/get_flot_axis_config.ts @@ -38,7 +38,7 @@ export const getFlotAxisConfig = ( const config: Config = { show: true }; - const axisType = get(columns, `${axis}.type`); + const axisType = get(columns, [axis, `type`]); if (isAxisConfig(argValue)) { const { position, min, max, tickSize } = argValue; diff --git a/x-pack/plugins/canvas/public/lib/filter.ts b/x-pack/plugins/canvas/public/lib/filter.ts index 767cf53e16f6d..fade544b2bc80 100644 --- a/x-pack/plugins/canvas/public/lib/filter.ts +++ b/x-pack/plugins/canvas/public/lib/filter.ts @@ -65,7 +65,7 @@ const excludeFiltersByGroups = (filters: Ast[], filterExprAst: AstFunction) => { const groupsToExclude = filterExprAst.arguments.group ?? []; const removeUngrouped = filterExprAst.arguments.ungrouped?.[0] ?? false; return filters.filter((filter) => { - const groups: string[] = get(filter, 'chain[0].arguments.filterGroup', []).filter( + const groups: string[] = (get(filter, 'chain[0].arguments.filterGroup', []) as string[]).filter( (group: string) => group !== '' ); const noNeedToExcludeByGroup = !( @@ -89,7 +89,7 @@ const includeFiltersByGroups = ( const groupsToInclude = filterExprAst.arguments.group ?? []; const includeOnlyUngrouped = filterExprAst.arguments.ungrouped?.[0] ?? false; return filters.filter((filter) => { - const groups: string[] = get(filter, 'chain[0].arguments.filterGroup', []).filter( + const groups: string[] = (get(filter, 'chain[0].arguments.filterGroup', []) as string[]).filter( (group: string) => group !== '' ); const needToIncludeByGroup = diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serialize_migrate_and_allocate_actions.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serialize_migrate_and_allocate_actions.ts index a99b340469808..bb1a485d0c659 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serialize_migrate_and_allocate_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serialize_migrate_and_allocate_actions.ts @@ -66,14 +66,14 @@ export const serializeMigrateAndAllocateActions = ( if (!isEmpty(originalActions?.allocate?.include)) { actions.allocate = { ...actions.allocate, - include: { ...originalActions?.allocate?.include }, + include: { ...(originalActions.allocate!.include as {}) }, }; } if (!isEmpty(originalActions?.allocate?.exclude)) { actions.allocate = { ...actions.allocate, - exclude: { ...originalActions?.allocate?.exclude }, + exclude: { ...(originalActions.allocate!.exclude as {}) }, }; } break; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 76b2e17cb002e..dffe9628f29ec 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -695,6 +695,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource ); } + // @ts-expect-error hit's type is `SearchHit<{}>` while `flattenHit` expects `Record` const properties = indexPattern.flattenHit(hit) as Record; indexPattern.metaFields.forEach((metaField: string) => { if (!this._getTooltipPropertyNames().includes(metaField)) { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx index 72e55b2564e35..d646b1e8b9ff5 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx @@ -82,7 +82,7 @@ function groupFieldsByOrigin(fields: StyleField[]) { type Props = { fields: StyleField[]; - selectedFieldName: string; + selectedFieldName?: string; onChange: ({ field }: { field: StyleField | null }) => void; styleName: VECTOR_STYLES; } & Omit< diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index c69e4d855ad90..e3751d9424dd0 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -301,9 +301,7 @@ export class DynamicStyleProperty return this.getDataMappingFunction() === DATA_MAPPING_FUNCTION.INTERPOLATE ? this._field.getExtendedStatsFieldMetaRequest() : this._field.getPercentilesFieldMetaRequest( - this.getFieldMetaOptions().percentiles !== undefined - ? this.getFieldMetaOptions().percentiles - : DEFAULT_PERCENTILES + this.getFieldMetaOptions().percentiles ?? DEFAULT_PERCENTILES ); } @@ -331,7 +329,7 @@ export class DynamicStyleProperty return this.usesFeatureState() ? MB_LOOKUP_FUNCTION.FEATURE_STATE : MB_LOOKUP_FUNCTION.GET; } - getFieldMetaOptions() { + getFieldMetaOptions(): FieldMetaOptions { const fieldMetaOptions = _.get(this.getOptions(), 'fieldMetaOptions', { isEnabled: true }); // In 8.0, UI changed to not allow setting isEnabled to false when fieldMeta from local not supported diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx index 1f54801118f30..96b1c99e98bbc 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx @@ -69,7 +69,7 @@ export class Join extends Component { this._isMounted = false; } - async _loadRightFields(indexPatternId: string) { + async _loadRightFields(indexPatternId?: string) { if (!indexPatternId) { return; } diff --git a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts index 771e40fc13336..2220cf86d3edb 100644 --- a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts +++ b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts @@ -1237,11 +1237,11 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu // If the job uses aggregation or scripted fields, and if it's a config we don't support // use model plot data if model plot is enabled // else if source data can be plotted, use that, otherwise model plot will be available. - // @ts-ignore + // @ts-expect-error const useSourceData = isSourceDataChartableForDetector(job, detectorIndex); if (useSourceData) { - const datafeedQuery = get(config, 'datafeedConfig.query', null); + const datafeedQuery = get(config, 'datafeedConfig.query'); try { return await fetchMetricData( diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_title.ts b/x-pack/plugins/monitoring/public/application/hooks/use_title.ts index a4661e71eb399..ace9bd92b0a2e 100644 --- a/x-pack/plugins/monitoring/public/application/hooks/use_title.ts +++ b/x-pack/plugins/monitoring/public/application/hooks/use_title.ts @@ -11,7 +11,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; // TODO: verify that works for all pages export function useTitle(cluster: string, suffix: string) { const { services } = useKibana(); - let clusterName = get(cluster, 'cluster_name'); + let clusterName: string | undefined = get(cluster, 'cluster_name'); clusterName = clusterName ? `- ${clusterName}` : ''; suffix = suffix ? `- ${suffix}` : ''; diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx index 1c9f0b3ee9acc..073da0ca73bb4 100644 --- a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx +++ b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx @@ -92,7 +92,7 @@ export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid const clusterUuid = globalState.cluster_uuid; if (!clusterUuid) { - const liveClusterUuid: string = get(data, '_meta.liveClusterUuid'); + const liveClusterUuid: string | undefined = get(data, '_meta.liveClusterUuid'); const migratedEsNodes = Object.values(get(data, 'elasticsearch.byUuid', {})).filter( (node: any) => node.isPartiallyMigrated || node.isFullyMigrated ); diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts index ba099f1e7e7a8..ede6d1edf0848 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts @@ -41,6 +41,6 @@ describe('fetchLicenseType', () => { })), } as unknown as ElasticsearchClient; const result = await fetchLicenseType(customCallCluster, availableCcs, clusterUuid); - expect(result).toStrictEqual(null); + expect(result).toBeUndefined(); }); }); diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts index 6601c350d4922..86761262c886f 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts @@ -56,5 +56,5 @@ export async function fetchLicenseType( }, }; const response = await client.search(params); - return get(response, 'hits.hits[0]._source.license.type', null); + return get(response, 'hits.hits[0]._source.license.type') as string | undefined; } diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/types.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/types.ts index 2f2437b325b5a..8914d20942551 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/types.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/types.ts @@ -12,7 +12,7 @@ export interface MonitoringUsage { export interface MonitoringClusterStackProductUsage { clusterUuid: string; - license: string; + license?: string; metricbeatUsed: boolean; elasticsearch: StackProductUsage; logstash: StackProductUsage; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts index 77c96e8b6138a..ddd9b79d09cb4 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts @@ -82,7 +82,7 @@ describe('fetchCpuUsageNodeStats', () => { containerUsage: undefined, containerPeriods: undefined, containerQuota: undefined, - ccs: null, + ccs: undefined, }, ]); }); @@ -149,7 +149,7 @@ describe('fetchCpuUsageNodeStats', () => { containerUsage: 10, containerPeriods: 5, containerQuota: 50, - ccs: null, + ccs: undefined, }, ]); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts index 8037ad94e6764..b74b1b78b495b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts @@ -170,12 +170,12 @@ export async function fetchCpuUsageNodeStats( const stat = { clusterUuid: clusterBucket.key, nodeId: node.key, - nodeName: get(node, 'name.buckets[0].key'), - cpuUsage: get(node, 'average_cpu.value'), - containerUsage: get(lastBucket, 'usage_deriv.normalized_value'), - containerPeriods: get(lastBucket, 'periods_deriv.normalized_value'), - containerQuota: get(node, 'average_quota.value'), - ccs: indexName.includes(':') ? indexName.split(':')[0] : null, + nodeName: get(node, 'name.buckets[0].key') as unknown as string, + cpuUsage: get(node, 'average_cpu.value') as number, + containerUsage: get(lastBucket, 'usage_deriv.normalized_value') as unknown as number, + containerPeriods: get(lastBucket, 'periods_deriv.normalized_value') as unknown as number, + containerQuota: get(node, 'average_quota.value') as unknown as number, + ccs: indexName.includes(':') ? indexName.split(':')[0] : undefined, }; stats.push(stat); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts index 0e6fea87904e5..7a70a8594936f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts @@ -105,14 +105,14 @@ describe('fetchMissingMonitoringData', () => { nodeName: 'nodeName1', clusterUuid: 'clusterUuid1', gapDuration: 1, - ccs: null, + ccs: undefined, }, { nodeId: 'nodeUuid2', nodeName: 'nodeName2', clusterUuid: 'clusterUuid1', gapDuration: 8, - ccs: null, + ccs: undefined, }, ]); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index f49d068864c69..35f37c14b98ac 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -163,7 +163,7 @@ export async function fetchMissingMonitoringData( nodeName, clusterUuid, gapDuration: differenceInMs, - ccs: indexName.includes(':') ? indexName.split(':')[0] : null, + ccs: indexName.includes(':') ? indexName.split(':')[0] : undefined, }; } } diff --git a/x-pack/plugins/monitoring/server/lib/details/get_series.ts b/x-pack/plugins/monitoring/server/lib/details/get_series.ts index 3c1844a8cf0ab..3b325864d8036 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_series.ts +++ b/x-pack/plugins/monitoring/server/lib/details/get_series.ts @@ -64,7 +64,7 @@ function defaultCalculation( metric?: Metric, defaultSizeInSeconds?: number ) { - const legacyValue: number = get(bucket, key, null); + const legacyValue: number = get(bucket, key, -1); const mbValue = bucket.metric_mb_deriv?.normalized_value ?? null; let value; if (mbValue !== null && !isNaN(mbValue) && mbValue > 0) { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts index 17a8909752103..d5af43e216834 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts @@ -37,7 +37,7 @@ export function handleResponse( cluster, 'elasticsearch.cluster.stats.state.master_node', get(cluster, 'cluster_state.master_node') - ); + ) as string; nodes = resp.aggregations?.nodes.buckets.reduce(normalizeNodeShards(masterNode), {}) ?? []; } diff --git a/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts b/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts index 8e1cb8930f804..e83818052f54b 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts @@ -66,7 +66,7 @@ export class QuotaMetric extends Metric { }; this.calculation = (bucket: object) => { - const quota = get(bucket, 'quota.value'); + const quota = get(bucket, 'quota.value', 0); const deltaUsageDerivNormalizedValue = get(bucket, 'usage_deriv.normalized_value'); const periodsDerivNormalizedValue = get(bucket, 'periods_deriv.normalized_value'); diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts index c6c49222e20fa..7cc9ea644b599 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts @@ -82,7 +82,7 @@ export class DifferenceMetric extends ElasticsearchMetric { this.getFields = () => [`${fieldSource}.${metric}`, `${fieldSource}.${metric2}`]; this.calculation = (bucket: object) => { - return _.get(bucket, 'metric_max.value') - _.get(bucket, 'metric2_max.value'); + return _.get(bucket, 'metric_max.value', 0) - _.get(bucket, 'metric2_max.value', 0); }; } } @@ -369,7 +369,8 @@ export class WriteThreadPoolRejectedMetric extends ElasticsearchMetric { const write = _.get(bucket, 'write_deriv.normalized_value', null); if (index !== null || bulk !== null || write !== null) { - const valueOrZero = (value: number) => (value < 0 ? 0 : value || 0); + const valueOrZero = (value: number | null) => + value === null || value < 0 ? 0 : value || 0; return valueOrZero(index) + valueOrZero(bulk) + valueOrZero(write); } @@ -394,7 +395,7 @@ export class MillisecondsToSecondsMetric extends ElasticsearchMetric { }), }); this.calculation = (bucket: object) => { - return _.get(bucket, 'metric.value') / 1000; + return _.get(bucket, 'metric.value', 0) / 1000; }; } } diff --git a/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts b/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts index 790f761637dbd..fd150864c6b2e 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts @@ -301,8 +301,8 @@ export class LogstashPipelineQueueSizeMetric extends LogstashMetric { }, }; this.calculation = (bucket: object) => { - const legacyQueueSize = _.get(bucket, 'pipelines.total_queue_size_for_node.value'); - const mbQueueSize = _.get(bucket, 'pipelines_mb.total_queue_size_for_node.value'); + const legacyQueueSize = _.get(bucket, 'pipelines.total_queue_size_for_node.value', 0); + const mbQueueSize = _.get(bucket, 'pipelines_mb.total_queue_size_for_node.value', 0); return Math.max(legacyQueueSize, mbQueueSize); }; } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts index 137175010cb22..7c939407ef156 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts @@ -328,6 +328,7 @@ export function ccrRoute(server: MonitoringCore) { ); const follows = remoteCluster ? `${leaderIndex} on ${remoteCluster}` : leaderIndex; + // @ts-expect-error `shards.error` type mismatch (error: string | undefined vs. error: { error: string | undefined }) const shards: CcrShard[] = get(bucket, 'by_shard_id.buckets').map( (shardBucket: CcrShardBucket) => buildShardStats({ bucket, fullStats, shardBucket }) ); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts index d420056d4ec0e..9aed104920e83 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts @@ -91,7 +91,7 @@ export function esIndexRoute(server: MonitoringCore) { cluster, 'elasticsearch.cluster.stats.state.state_uuid', get(cluster, 'cluster_state.state_uuid') - ); + ) as string; const allocationOptions = { shardFilter, stateUuid, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts index a07b55b9be635..763f8005abc93 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts @@ -94,6 +94,7 @@ export function esNodeRoute(server: MonitoringCore) { includeNodes: true, nodeUuid, }); + // @ts-expect-error `clusterState.master_node` types are incompatible const nodeSummary = await getNodeSummary(req, clusterState, shardStats, { clusterUuid, nodeUuid, @@ -119,7 +120,7 @@ export function esNodeRoute(server: MonitoringCore) { cluster, 'cluster_state.state_uuid', get(cluster, 'elasticsearch.cluster.stats.state.state_uuid') - ); + )!; const allocationOptions = { shardFilter, stateUuid, diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts index acae6ff11e12c..e8b53a1ed1adc 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts @@ -156,12 +156,14 @@ function groupInstancesByCluster( // hits are sorted arbitrarily by product UUID instances.map((instance) => { const clusterUuid = instance._source!.cluster_uuid; - const version: string | undefined = get( - instance, - `_source.${product}_stats.${product}.version` - ); - const cloud: CloudEntry | undefined = get(instance, `_source.${product}_stats.cloud`); - const os: OSData | undefined = get(instance, `_source.${product}_stats.os`); + const version: string | undefined = get(instance, [ + `_source`, + `${product}_stats`, + product, + `version`, + ]); + const cloud: CloudEntry | undefined = get(instance, [`_source`, `${product}_stats`, `cloud`]); + const os: OSData | undefined = get(instance, [`_source`, `${product}_stats`, `os`]); if (clusterUuid) { let cluster = clusterMap.get(clusterUuid); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts b/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts index 32692849d8d7c..2237548f2d325 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts @@ -90,9 +90,9 @@ function onPackagePolicyDelete({ const internalESClient = coreStart.elasticsearch.client.asInternalUser; - const [agentConfigApiKeyId] = get(packagePolicy, AGENT_CONFIG_API_KEY_PATH).split(':'); + const [agentConfigApiKeyId] = get(packagePolicy, AGENT_CONFIG_API_KEY_PATH, '').split(':'); - const [sourceMapApiKeyId] = get(packagePolicy, SOURCE_MAP_API_KEY_PATH).split(':'); + const [sourceMapApiKeyId] = get(packagePolicy, SOURCE_MAP_API_KEY_PATH, '').split(':'); logger.debug( `Deleting API keys: ${agentConfigApiKeyId}, ${sourceMapApiKeyId} (package policy: ${packagePolicy.id})` diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/helpers.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/helpers.ts index ff3147def0d77..84bd07dee5c47 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/helpers.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/helpers.ts @@ -70,7 +70,7 @@ export const getChartName = ( * just returns null if the color doesn't exists in the overrides. */ export const getChartColor = (seriesOverrides: SeriesOverrides | undefined, seriesId: string) => { - const rawColor: string | null = seriesOverrides + const rawColor: string | null | undefined = seriesOverrides ? get(seriesOverrides, [seriesId, 'color']) : null; if (!rawColor) { diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts index a23105f08cebf..4bd0b16212e11 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts @@ -44,8 +44,8 @@ export const getLogRateAnalysisEQQuery = ( return; } - const group: Group[] | undefined = get(alert, 'fields["kibana.alert.group"]'); - const optionalFilter: string | undefined = get(params.searchConfiguration, 'query.query'); + const group = get(alert, 'fields["kibana.alert.group"]') as Group[] | undefined; + const optionalFilter = get(params.searchConfiguration, 'query.query') as string | undefined; const groupByFilters = getGroupFilters(group); const boolQuery = buildEsQuery({ kuery: getKuery(params.criteria[0].metrics, optionalFilter), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx index fd2bc98a048b0..45ab4a1c969c5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx @@ -21,7 +21,7 @@ import type { FC } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; -import type { Cancelable } from 'lodash'; +import type { DebouncedFunc } from 'lodash'; import { debounce } from 'lodash'; import type { EqlOptionsData, @@ -79,7 +79,7 @@ export const EqlQueryBarFooter: FC = ({ }) => { const [openEqlSettings, setIsOpenEqlSettings] = useState(false); const [localSize, setLocalSize] = useState(optionsSelected?.size ?? 100); - const debounceSize = useRef(); + const debounceSize = useRef>(); const openEqlSettingsHandler = useCallback(() => { setIsOpenEqlSettings(true); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx index 47d955c0f5f74..60a90709391c4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx @@ -969,12 +969,12 @@ export const getAlertHighlightedFields = ( alertData: AlertData, ruleCustomHighlightedFields: string[] ): EventSummaryField[] => { - const eventCategory = get(alertData, EVENT_CATEGORY); + const eventCategory = get(alertData, EVENT_CATEGORY) as string | string[]; const eventCode = get(alertData, EVENT_CODE); const eventRuleType = get(alertData, KIBANA_ALERT_RULE_TYPE); const eventCategories = { primaryEventCategory: Array.isArray(eventCategory) ? eventCategory[0] : eventCategory, - allEventCategories: [eventCategory], + allEventCategories: Array.isArray(eventCategory) ? eventCategory : [eventCategory], }; const fieldsToDisplay = getEventFieldsToDisplay({ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/suppressed_alerts.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/suppressed_alerts.tsx index b817be7a308b5..2c24b5c595c52 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/suppressed_alerts.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/suppressed_alerts.tsx @@ -13,6 +13,7 @@ import { EuiBetaBadge, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { ExpandablePanel } from '@kbn/security-solution-common'; +import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID, SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID, @@ -61,7 +62,7 @@ export const SuppressedAlerts: React.FC = ({ values={{ count: alertSuppressionCount }} /> - {isSuppressionRuleInGA(ruleType) ? null : ( + {isSuppressionRuleInGA(ruleType as Type) ? null : ( { showCases || showSuppressedAlerts; - const ruleType = get(dataAsNestedObject, ALERT_RULE_TYPE)?.[0]; + const ruleType = get(dataAsNestedObject, ALERT_RULE_TYPE)?.[0] as Type | undefined; const link = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts index 1f4619695978d..db2f201c97448 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts @@ -39,6 +39,9 @@ export const responseActionTasks = ( } const signed = get(newActionDoc, '_source.signed'); + if (!signed) { + throw new Error('no signed data in the action doc'); + } const signedDataBuffer = Buffer.from(signed.data, 'base64'); const signedDataJson = JSON.parse(signedDataBuffer.toString()); const tamperedData = { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/helpers.ts index 1858df09a260a..0422eeae35f28 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/helpers.ts @@ -19,6 +19,7 @@ const getIndicatorEcs = (data: Ecs): ThreatIndicatorEcs[] => { } else if (!Array.isArray(threatData)) { return [threatData]; } + // @ts-expect-error the returned type is ThreatEnrichmentEcs[] return threatData; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx index 7a4118791f91c..bc0c74a5ee5b0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.test.tsx @@ -82,7 +82,7 @@ describe('threatMatchRowRenderer', () => { const NO_OF_MATCHES = 20; const largeNoOfIndicatorMatches = new Array(NO_OF_MATCHES) .fill({}) - .map(() => get(threatMatchData, ENRICHMENT_DESTINATION_PATH)[0] as Fields); + .map(() => get(threatMatchData, ENRICHMENT_DESTINATION_PATH)![0] as Fields); const modThreatMatchData: typeof threatMatchData = { ...threatMatchData, diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx index 86adab17e83f3..118c880a697c0 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx @@ -15,7 +15,6 @@ import { } from '../../../common/mocks/constants/session_view_process.mock'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { ProcessDeps, ProcessTreeNode } from '.'; -import { Cancelable } from 'lodash'; import { DEBOUNCE_TIMEOUT } from '../../../common/constants'; import { useDateFormat } from '../../hooks'; @@ -101,10 +100,10 @@ describe('ProcessTreeNode component', () => { current: { ...props.scrollerRef.current, clientHeight: -500, - addEventListener: (_event: string, scrollFn: (() => void) & Cancelable) => { + addEventListener: (_event: string, scrollFn: () => void) => { scrollFn(); }, - removeEventListener: (_event: string, _fn: (() => void) & Cancelable) => {}, + removeEventListener: (_event: string, _fn: () => void) => {}, }, } as RefObject; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts index 0aa64fca908d0..0ada7928aeda3 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts @@ -297,7 +297,7 @@ function checkHitsForDateOutOfRange( const outsideTimeRange = 'outside the query time range'; for (const hit of hits) { - const dateVal = get(hit, `_source.${timeField}`); + const dateVal = get(hit, [`_source`, timeField]); const epochDate = getEpochDateFromString(dateVal); if (epochDate) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/api_key.ts index 2bbdabbf03c18..bbb97281b82b1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/api_key.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/api_key.ts @@ -184,7 +184,7 @@ export default function apiKeyBackfillTests({ getService }: FtrProviderContext) // check that the ad hoc run SO was created const adHocRunSO1 = (await getAdHocRunSO(result[0].id)) as SavedObject; - const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params'); + const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params')!; expect(typeof adHocRun1.apiKeyId).to.be('string'); expect(typeof adHocRun1.apiKeyToUse).to.be('string'); expect(typeof adHocRun1.createdAt).to.be('string'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/delete_rule.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/delete_rule.ts index d0f3fdf92280b..942c8efa615a4 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/delete_rule.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/delete_rule.ts @@ -103,13 +103,13 @@ export default function deleteRuleForBackfillTests({ getService }: FtrProviderCo // check that the ad hoc run SOs were created const adHocRunSO1 = (await getAdHocRunSO(backfillId1)) as SavedObject; - const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params'); + const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params')!; expect(adHocRun1).not.to.be(undefined); const adHocRunSO2 = (await getAdHocRunSO(backfillId2)) as SavedObject; - const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params'); + const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params')!; expect(adHocRun2).not.to.be(undefined); const adHocRunSO3 = (await getAdHocRunSO(backfillId3)) as SavedObject; - const adHocRun3: AdHocRunSO = get(adHocRunSO3, 'ad_hoc_run_params'); + const adHocRun3: AdHocRunSO = get(adHocRunSO3, 'ad_hoc_run_params')!; expect(adHocRun3).not.to.be(undefined); // check that the scheduled tasks were created diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/schedule.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/schedule.ts index 4f4cd403cbe82..8f25a3e181c66 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/schedule.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/schedule.ts @@ -232,9 +232,15 @@ export default function scheduleBackfillTests({ getService }: FtrProviderContext // check that the ad hoc run SO was created const adHocRunSO1 = (await getAdHocRunSO(result[0].id)) as SavedObject; - const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params'); + const adHocRun1: AdHocRunSO = get( + adHocRunSO1, + 'ad_hoc_run_params' + ) as unknown as AdHocRunSO; const adHocRunSO2 = (await getAdHocRunSO(result[1].id)) as SavedObject; - const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params'); + const adHocRun2: AdHocRunSO = get( + adHocRunSO2, + 'ad_hoc_run_params' + ) as unknown as AdHocRunSO; expect(typeof adHocRun1.apiKeyId).to.be('string'); expect(typeof adHocRun1.apiKeyToUse).to.be('string'); @@ -435,11 +441,11 @@ export default function scheduleBackfillTests({ getService }: FtrProviderContext // check that the ad hoc run SO was created const adHocRunSO1 = (await getAdHocRunSO(result[0].id)) as SavedObject; - const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params'); + const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params')!; const adHocRunSO2 = (await getAdHocRunSO(result[1].id)) as SavedObject; - const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params'); + const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params')!; const adHocRunSO3 = (await getAdHocRunSO(result[2].id)) as SavedObject; - const adHocRun3: AdHocRunSO = get(adHocRunSO3, 'ad_hoc_run_params'); + const adHocRun3: AdHocRunSO = get(adHocRunSO3, 'ad_hoc_run_params')!; expect(typeof adHocRun1.apiKeyId).to.be('string'); expect(typeof adHocRun1.apiKeyToUse).to.be('string'); @@ -940,11 +946,11 @@ export default function scheduleBackfillTests({ getService }: FtrProviderContext // check that the expected ad hoc run SOs were created const adHocRunSO1 = (await getAdHocRunSO(result[0].id)) as SavedObject; - const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params'); + const adHocRun1: AdHocRunSO = get(adHocRunSO1, 'ad_hoc_run_params')!; const adHocRunSO2 = (await getAdHocRunSO(result[1].id)) as SavedObject; - const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params'); + const adHocRun2: AdHocRunSO = get(adHocRunSO2, 'ad_hoc_run_params')!; const adHocRunSO3 = (await getAdHocRunSO(result[5].id)) as SavedObject; - const adHocRun3: AdHocRunSO = get(adHocRunSO3, 'ad_hoc_run_params'); + const adHocRun3: AdHocRunSO = get(adHocRunSO3, 'ad_hoc_run_params')!; expect(typeof adHocRun1.apiKeyId).to.be('string'); expect(typeof adHocRun1.apiKeyToUse).to.be('string'); diff --git a/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy.spec.ts b/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy.spec.ts index d78a6fa6d99d6..9ef09aaf58cb0 100644 --- a/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy.spec.ts +++ b/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy.spec.ts @@ -142,7 +142,7 @@ export default function ApiTest(ftrProviderContext: FtrProviderContext) { }); it('has api key that provides access to source maps only', async () => { - const [id, apiKey] = get(apmPackagePolicy, SOURCE_MAP_API_KEY_PATH).split(':'); + const [id, apiKey] = get(apmPackagePolicy, SOURCE_MAP_API_KEY_PATH, '').split(':'); expect(id).to.not.be.empty(); expect(apiKey).to.not.be.empty(); @@ -152,7 +152,7 @@ export default function ApiTest(ftrProviderContext: FtrProviderContext) { }); it('has api api key that provides access to the agent configurations index', async () => { - const [id, apiKey] = get(apmPackagePolicy, AGENT_CONFIG_API_KEY_PATH).split(':'); + const [id, apiKey] = get(apmPackagePolicy, AGENT_CONFIG_API_KEY_PATH, '').split(':'); expect(id).to.not.be.empty(); expect(apiKey).to.not.be.empty(); @@ -165,7 +165,7 @@ export default function ApiTest(ftrProviderContext: FtrProviderContext) { }); it('throws when querying agent config index with source map api key', async () => { - const [id, apiKey] = get(apmPackagePolicy, SOURCE_MAP_API_KEY_PATH).split(':'); + const [id, apiKey] = get(apmPackagePolicy, SOURCE_MAP_API_KEY_PATH, '').split(':'); expect(id).to.not.be.empty(); expect(apiKey).to.not.be.empty(); @@ -189,7 +189,7 @@ export default function ApiTest(ftrProviderContext: FtrProviderContext) { }); it('sets the expected agent configs on the new package policy object', async () => { - const agentConfigs = get(packagePolicyWithAgentConfig, AGENT_CONFIG_PATH); + const agentConfigs = get(packagePolicyWithAgentConfig, AGENT_CONFIG_PATH)!; const { service, config } = agentConfigs[0]; expect(service).to.eql({}); expect(config).to.eql({ transaction_sample_rate: '0.55' }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts b/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts index abb5f8c0d25f3..671a32c5893a6 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts @@ -56,8 +56,8 @@ export default function ({ getService }: FtrProviderContext) { q: 'type:kql-telemetry', }) .then((response) => { - const kqlTelemetryDoc = get(response, 'hits.hits[0]._source.kql-telemetry'); - expect(kqlTelemetryDoc.optInCount).to.be(1); + const optInCount = get(response, 'hits.hits[0]._source.kql-telemetry.optInCount'); + expect(optInCount).to.be(1); }); }); @@ -73,8 +73,8 @@ export default function ({ getService }: FtrProviderContext) { q: 'type:kql-telemetry', }) .then((response) => { - const kqlTelemetryDoc = get(response, 'hits.hits[0]._source.kql-telemetry'); - expect(kqlTelemetryDoc.optOutCount).to.be(1); + const optOutCount = get(response, 'hits.hits[0]._source.kql-telemetry.optOutCount'); + expect(optOutCount).to.be(1); }); }); diff --git a/yarn.lock b/yarn.lock index 86fbcde58bc39..e9844384de3c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10858,11 +10858,6 @@ "@types/node" "*" "@types/webpack" "^4" -"@types/lodash@^4.14.159": - version "4.14.159" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" - integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== - "@types/lodash@^4.14.167": version "4.14.184" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" @@ -10873,6 +10868,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== +"@types/lodash@^4.17.10": + version "4.17.10" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.10.tgz#64f3edf656af2fe59e7278b73d3e62404144a6e6" + integrity sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ== + "@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" From e0251df6bd944fc53c58b9ee73c0b5d2eb892197 Mon Sep 17 00:00:00 2001 From: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:46:56 +0200 Subject: [PATCH 04/84] [Lens] escape backslash characters in the formula input (#196171) ## Summary Ensures that backslashes are properly escaped in addition to single quotes in formula --- .../form_based/operations/definitions/formula/generate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts index a2f94fb437062..b13c3ff261207 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts @@ -79,7 +79,7 @@ export function generateFormula( } previousFormula += (previousColumn.filter.language === 'kuery' ? 'kql=' : 'lucene=') + - `'${previousColumn.filter.query.replace(/'/g, `\\'`)}'`; // replace all + `'${previousColumn.filter.query.replace(/\\/g, '\\\\').replace(/'/g, `\\'`)}'`; // replace all } if (previousColumn.timeShift) { if ( From ac5b14b1254219f79dddd5da0ab9c1ff8d1ea0c1 Mon Sep 17 00:00:00 2001 From: Robert Jaszczurek <92210485+rbrtj@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:52:06 +0200 Subject: [PATCH 05/84] [ML] Data visualizer: Add icons for semantic text, sparse vector and dense vector (#196069) ## Summary Added support for `semantic_text`, `sparse_vector` and `dense_vector` in the Data visualizer and Field Statistics. For [#192161](https://github.com/elastic/kibana/issues/192161) | Before | After | | ------------- | ------------- | | ![image](https://github.com/user-attachments/assets/d87de954-bae0-46d3-9934-e6e6a6424ee0) | ![image](https://github.com/user-attachments/assets/a6fced39-e227-43ea-9062-9add27aad8fd) | | ![image](https://github.com/user-attachments/assets/4c8005e3-439f-4dfc-898b-8158835d803f) | ![image](https://github.com/user-attachments/assets/a9d501cc-84f9-4394-9ffb-a6fa62269cde) | --- x-pack/plugins/data_visualizer/common/constants.ts | 4 ++++ .../application/common/util/field_types_utils.ts | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/data_visualizer/common/constants.ts b/x-pack/plugins/data_visualizer/common/constants.ts index ff277b9bb4785..4f552f45f61a4 100644 --- a/x-pack/plugins/data_visualizer/common/constants.ts +++ b/x-pack/plugins/data_visualizer/common/constants.ts @@ -47,6 +47,9 @@ export const SUPPORTED_FIELD_TYPES = { NESTED: 'nested', STRING: 'string', TEXT: 'text', + SEMANTIC_TEXT: 'semantic_text', + DENSE_VECTOR: 'dense_vector', + SPARSE_VECTOR: 'sparse_vector', VERSION: 'version', UNKNOWN: 'unknown', } as const; @@ -73,3 +76,4 @@ export const featureTitle = i18n.translate('xpack.dataVisualizer.title', { defaultMessage: 'Upload a file', }); export const featureId = `file_data_visualizer`; +export const SUPPORTED_FIELD_TYPES_LIST: string[] = Object.values(SUPPORTED_FIELD_TYPES); diff --git a/x-pack/plugins/data_visualizer/public/application/common/util/field_types_utils.ts b/x-pack/plugins/data_visualizer/public/application/common/util/field_types_utils.ts index 0b79cd4079f76..ad9aaf6413813 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/util/field_types_utils.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/util/field_types_utils.ts @@ -8,7 +8,7 @@ import type { DataViewField } from '@kbn/data-views-plugin/public'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { getFieldType } from '@kbn/field-utils/src/utils/get_field_type'; -import { SUPPORTED_FIELD_TYPES } from '../../../../common/constants'; +import { SUPPORTED_FIELD_TYPES, SUPPORTED_FIELD_TYPES_LIST } from '../../../../common/constants'; // convert kibana types to ML Job types // this is needed because kibana types only have string and not text and keyword. @@ -26,6 +26,15 @@ export function kbnTypeToSupportedType(field: DataViewField) { } break; + case KBN_FIELD_TYPES.UNKNOWN: + const maybeFieldType = field.esTypes?.[0]; + if (maybeFieldType && SUPPORTED_FIELD_TYPES_LIST.includes(maybeFieldType)) { + type = maybeFieldType; + } else { + type = getFieldType(field); + } + break; + default: type = getFieldType(field); break; From 90b4ba556165ce70da71acf2cb9b43d324412cda Mon Sep 17 00:00:00 2001 From: dkirchan <55240027+dkirchan@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:16:31 +0300 Subject: [PATCH 06/84] [Security Solution][Serverless] Logging - Fix explore test issue (#195941) ## Summary This PR addresses two points: - First it introduces the project information logging (not any sensitive data like pwd etc) for better troubleshooting. This will allow teams to be able to get the project ID and the organisation ID in order to search for project logs etc in the overview consoles. - Explore test issue: A specific spec file was crashing causing Buildkite timeout which was taking 5 hours to be reached. Something seems to be going wrong with the order of the tests within the specific spec suite, **specifically in CI**. Potentially the configuration of the machines where the test run. After a lot of investigation the order is changed and the `Copy value` test was moved to the top of the spec file. This allows the proper execution of all the tests. Pending further investigation. Me and @MadameSheema tested this locally and it always passes without any issues. Also I tried to increase the resources of the agent assigned in Buildkite to run the tests but this still does not seem to be resolving the issue. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../run_cypress/parallel_serverless.ts | 53 +++++++++++++------ .../project_handler/cloud_project_handler.ts | 6 +-- .../project_handler/project_handler.ts | 2 +- .../project_handler/proxy_project_handler.ts | 6 +-- .../scripts/mki_api_ftr_execution.ts | 33 +++++++++--- .../e2e/explore/network/hover_actions.cy.ts | 16 +++--- 6 files changed, 76 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 8e680e8ebc451..0b426cf1e8c20 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -93,7 +93,11 @@ export function proxyHealthcheck(proxyUrl: string): Promise { } // Wait until elasticsearch status goes green -export function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: string): Promise { +export function waitForEsStatusGreen( + esUrl: string, + auth: string, + projectId: string +): Promise { const fetchHealthStatusAttempt = async (attemptNum: number) => { log.info(`Retry number ${attemptNum} to check if Elasticsearch is green.`); @@ -105,13 +109,13 @@ export function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: stri }) .catch(catchAxiosErrorFormatAndThrow); - log.info(`${runnerId}: Elasticsearch is ready with status ${response.data.status}.`); + log.info(`${projectId}: Elasticsearch is ready with status ${response.data.status}.`); }; const retryOptions = { onFailedAttempt: (error: Error | AxiosError) => { if (error instanceof AxiosError && error.code === 'ENOTFOUND') { log.info( - `${runnerId}: The Elasticsearch URL is not yet reachable. A retry will be triggered soon...` + `${projectId}: The Elasticsearch URL is not yet reachable. A retry will be triggered soon...` ); } }, @@ -127,7 +131,7 @@ export function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: stri export function waitForKibanaAvailable( kbUrl: string, auth: string, - runnerId: string + projectId: string ): Promise { const fetchKibanaStatusAttempt = async (attemptNum: number) => { log.info(`Retry number ${attemptNum} to check if kibana is available.`); @@ -139,19 +143,19 @@ export function waitForKibanaAvailable( }) .catch(catchAxiosErrorFormatAndThrow); if (response.data.status.overall.level !== 'available') { - throw new Error(`${runnerId}: Kibana is not available. A retry will be triggered soon...`); + throw new Error(`${projectId}: Kibana is not available. A retry will be triggered soon...`); } else { - log.info(`${runnerId}: Kibana status overall is ${response.data.status.overall.level}.`); + log.info(`${projectId}: Kibana status overall is ${response.data.status.overall.level}.`); } }; const retryOptions = { onFailedAttempt: (error: Error | AxiosError) => { if (error instanceof AxiosError && error.code === 'ENOTFOUND') { log.info( - `${runnerId}: The Kibana URL is not yet reachable. A retry will be triggered soon...` + `${projectId}: The Kibana URL is not yet reachable. A retry will be triggered soon...` ); } else { - log.info(`${runnerId}: ${error.message}`); + log.info(`${projectId}: ${error.message}`); } }, retries: 50, @@ -162,7 +166,7 @@ export function waitForKibanaAvailable( } // Wait for Elasticsearch to be accessible -export function waitForEsAccess(esUrl: string, auth: string, runnerId: string): Promise { +export function waitForEsAccess(esUrl: string, auth: string, projectId: string): Promise { const fetchEsAccessAttempt = async (attemptNum: number) => { log.info(`Retry number ${attemptNum} to check if can be accessed.`); @@ -178,7 +182,7 @@ export function waitForEsAccess(esUrl: string, auth: string, runnerId: string): onFailedAttempt: (error: Error | AxiosError) => { if (error instanceof AxiosError && error.code === 'ENOTFOUND') { log.info( - `${runnerId}: The elasticsearch url is not yet reachable. A retry will be triggered soon...` + `${projectId}: The elasticsearch url is not yet reachable. A retry will be triggered soon...` ); } }, @@ -447,7 +451,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} : (parseTestFileConfig(filePath).productTypes as ProductType[]); log.info(`Running spec file: ${filePath}`); - log.info(`${id}: Creating project ${PROJECT_NAME}...`); + log.info(`Creating project ${PROJECT_NAME}...`); // Creating project for the test to run const project = await cloudHandler.createSecurityProject( PROJECT_NAME, @@ -461,6 +465,21 @@ ${JSON.stringify(cypressConfigFile, null, 2)} return process.exit(1); } + log.info(` + ----------------------------------------------- + Project created with details: + ----------------------------------------------- + ID: ${project.id} + Name: ${project.name} + Region: ${project.region} + Elasticsearch URL: ${project.es_url} + Kibana URL: ${project.kb_url} + Product: ${project.product} + Organization ID: ${project.proxy_org_id} + Organization Name: ${project.proxy_org_name} + ----------------------------------------------- + `); + context.addCleanupTask(() => { let command: string; if (cloudHandler instanceof CloudHandler) { @@ -470,7 +489,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} }); // Reset credentials for elastic user - const credentials = await cloudHandler.resetCredentials(project.id, id); + const credentials = await cloudHandler.resetCredentials(project.id); if (!credentials) { log.error('Credentials could not be reset.'); @@ -485,13 +504,13 @@ ${JSON.stringify(cypressConfigFile, null, 2)} const auth = btoa(`${credentials.username}:${credentials.password}`); // Wait for elasticsearch status to go green. - await waitForEsStatusGreen(project.es_url, auth, id); + await waitForEsStatusGreen(project.es_url, auth, project.id); // Wait until Kibana is available - await waitForKibanaAvailable(project.kb_url, auth, id); + await waitForKibanaAvailable(project.kb_url, auth, project.id); // Wait for Elasticsearch to be accessible - await waitForEsAccess(project.es_url, auth, id); + await waitForEsAccess(project.es_url, auth, project.id); // Wait until application is ready await waitForKibanaLogin(project.kb_url, credentials); @@ -499,7 +518,6 @@ ${JSON.stringify(cypressConfigFile, null, 2)} // Check if proxy service is used to define which org executes the tests. const proxyOrg = cloudHandler instanceof ProxyHandler ? project.proxy_org_name : undefined; - log.info(`Proxy Organization used id : ${proxyOrg}`); // Normalized the set of available env vars in cypress const cyCustomEnv = { @@ -576,13 +594,14 @@ ${JSON.stringify(cypressConfigFile, null, 2)} failedSpecFilePaths.push(filePath); } // Delete serverless project - log.info(`${id} : Deleting project ${PROJECT_NAME}...`); + log.info(`Deleting project ${PROJECT_NAME} and ID ${project.id} ...`); await cloudHandler.deleteSecurityProject(project.id, PROJECT_NAME); } catch (error) { // False positive // eslint-disable-next-line require-atomic-updates result = error; failedSpecFilePaths.push(filePath); + log.error(`Cypress runner failed: ${error}`); } } return result; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts index 5ae476c17580e..2d41b9605b275 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts @@ -105,8 +105,8 @@ export class CloudHandler extends ProjectHandler { } // Method to reset the credentials for the created project. - resetCredentials(projectId: string, runnerId: string): Promise { - this.log.info(`${runnerId} : Reseting credentials`); + resetCredentials(projectId: string): Promise { + this.log.info(`${projectId} : Reseting credentials`); const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { const response = await axios.post( @@ -118,7 +118,7 @@ export class CloudHandler extends ProjectHandler { }, } ); - this.log.info('Credentials have ben reset'); + this.log.info('Credentials have been reset'); return { password: response.data.password, username: response.data.username, diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts index f84bc6d9961ce..6560a9a5cbdfa 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts @@ -75,7 +75,7 @@ export class ProjectHandler { throw new Error(this.DEFAULT_ERROR_MSG); } - resetCredentials(projectId: string, runnerId: string): Promise { + resetCredentials(projectId: string): Promise { throw new Error(this.DEFAULT_ERROR_MSG); } diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts index dfc97e9a422d8..ec7794389233f 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts @@ -104,8 +104,8 @@ export class ProxyHandler extends ProjectHandler { } // Method to reset the credentials for the created project. - resetCredentials(projectId: string, runnerId: string): Promise { - this.log.info(`${runnerId} : Reseting credentials`); + resetCredentials(projectId: string): Promise { + this.log.info(`${projectId} : Reseting credentials`); const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { const response = await axios.post( @@ -117,7 +117,7 @@ export class ProxyHandler extends ProjectHandler { }, } ); - this.log.info('Credentials have ben reset'); + this.log.info('Credentials have been reset'); return { password: response.data.password, username: response.data.username, diff --git a/x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts b/x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts index 7241d28f9c29b..f7a187715bd10 100644 --- a/x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts +++ b/x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts @@ -117,19 +117,36 @@ export const cli = () => { const productTypes = await parseProductTypes(log); // Creating project for the test to run + log.info(`${id}: Creating project ${PROJECT_NAME}...`); const project = await cloudHandler.createSecurityProject(PROJECT_NAME, productTypes); - // Check if proxy service is used to define which org executes the tests. - const proxyOrg = cloudHandler instanceof ProxyHandler ? project?.proxy_org_name : undefined; - log.info(`Proxy Organization used id : ${proxyOrg}`); if (!project) { log.error('Failed to create project.'); return process.exit(1); } + + log.info(` + ----------------------------------------------- + Project created with details: + ----------------------------------------------- + ID: ${project.id} + Name: ${project.name} + Region: ${project.region} + Elasticsearch URL: ${project.es_url} + Kibana URL: ${project.kb_url} + Product: ${project.product} + Organization ID: ${project.proxy_org_id} + Organization Name: ${project.proxy_org_name} + ----------------------------------------------- + `); + + // Check if proxy service is used to define which org executes the tests. + const proxyOrg = cloudHandler instanceof ProxyHandler ? project?.proxy_org_name : undefined; + let statusCode: number = 0; try { // Reset credentials for elastic user - const credentials = await cloudHandler.resetCredentials(project.id, id); + const credentials = await cloudHandler.resetCredentials(project.id); if (!credentials) { log.error('Credentials could not be reset.'); @@ -144,13 +161,13 @@ export const cli = () => { const auth = btoa(`${credentials.username}:${credentials.password}`); // Wait for elasticsearch status to go green. - await waitForEsStatusGreen(project.es_url, auth, id); + await waitForEsStatusGreen(project.es_url, auth, project.id); // Wait until Kibana is available - await waitForKibanaAvailable(project.kb_url, auth, id); + await waitForKibanaAvailable(project.kb_url, auth, project.id); // Wait for Elasticsearch to be accessible - await waitForEsAccess(project.es_url, auth, id); + await waitForEsAccess(project.es_url, auth, project.id); const FORMATTED_ES_URL = project.es_url.replace('https://', ''); const FORMATTED_KB_URL = project.kb_url.replace('https://', ''); @@ -175,7 +192,7 @@ export const cli = () => { statusCode = 1; } finally { // Delete serverless project - log.info(`${id} : Deleting project ${PROJECT_NAME}...`); + log.info(`Deleting project ${PROJECT_NAME} and ID ${project.id} ...`); await cloudHandler.deleteSecurityProject(project.id, PROJECT_NAME); } process.exit(statusCode); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/hover_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/hover_actions.cy.ts index 7d6ab6abdb8be..db00bc4d45bbb 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/hover_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/hover_actions.cy.ts @@ -48,6 +48,14 @@ describe('Hover actions', { tags: ['@ess', '@serverless'] }, () => { mouseoverOnToOverflowItem(); }); + it('Copy value', () => { + cy.document().then((doc) => cy.spy(doc, 'execCommand').as('execCommand')); + + clickOnCopyValue(); + + cy.get('@execCommand').should('have.been.calledOnceWith', 'copy'); + }); + it('Adds global filter - filter in', () => { clickOnFilterIn(); @@ -75,12 +83,4 @@ describe('Hover actions', { tags: ['@ess', '@serverless'] }, () => { clickOnShowTopN(); cy.get(TOP_N_CONTAINER).should('exist').should('contain.text', 'Top destination.domain'); }); - - it('Copy value', () => { - cy.document().then((doc) => cy.spy(doc, 'execCommand').as('execCommand')); - - clickOnCopyValue(); - - cy.get('@execCommand').should('have.been.calledOnceWith', 'copy'); - }); }); From 7235ed0425100bbf04ff157d0af7980875473c99 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Tue, 15 Oct 2024 11:38:44 +0200 Subject: [PATCH 07/84] [APM][Otel] Use `fields` instead of `_source` on APM queries (#195242) closes https://github.com/elastic/kibana/issues/192606 ## Summary v2 based on the work done in this PR https://github.com/elastic/kibana/pull/192608 and the suggestion from Dario https://github.com/elastic/kibana/pull/194424 This PR replaces the _source usage in APM queries with fields to support Otel data. The idea is to get rid of existing UI errors we have and make sure that otel data is shown correctly in the UI. One way to check it is using the [e2e PoC](https://github.com/elastic/otel-apm-e2e-poc/blob/main/README.md). --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine Co-authored-by: Jenny --- packages/kbn-apm-types/src/es_fields/apm.ts | 21 +- .../src/es_schemas/raw/apm_base_doc.ts | 6 +- .../src/es_schemas/raw/fields/cloud.ts | 18 +- .../src/es_schemas/raw/fields/container.ts | 4 +- .../src/es_schemas/raw/fields/http.ts | 4 +- .../src/es_schemas/raw/fields/kubernetes.ts | 2 +- .../src/es_schemas/raw/fields/observer.ts | 4 +- .../src/es_schemas/raw/fields/page.ts | 2 +- .../src/es_schemas/raw/fields/service.ts | 8 +- .../src/es_schemas/raw/fields/url.ts | 2 +- .../src/es_schemas/raw/fields/user.ts | 2 +- .../src/es_schemas/ui/fields/agent.ts | 2 +- .../object/flatten_object.test.ts | 12 ++ .../object/unflatten_object.test.ts | 40 ++++ .../object/unflatten_object.ts | 28 +++ .../observability_utils/tsconfig.json | 1 + .../__snapshots__/es_fields.test.ts.snap | 78 ++++++- .../apm/common/es_fields/es_fields.test.ts | 9 +- .../apm/common/service_metadata.ts | 58 ++++++ .../apm/common/waterfall/typings.ts | 9 +- .../error_sample_contextual_insight.tsx | 24 ++- .../error_sampler/error_sample_detail.tsx | 25 ++- .../error_sampler/error_tabs.tsx | 2 +- .../error_sampler/sample_summary.tsx | 4 +- ...redirect_to_transaction_detail_page_url.ts | 4 +- .../discover_links/discover_error_link.tsx | 14 +- .../metadata_table/error_metadata/index.tsx | 11 +- .../collect_data_telemetry/tasks.test.ts | 6 +- .../collect_data_telemetry/tasks.ts | 32 +-- .../get_destination_map.ts | 1 - .../apm/server/lib/helpers/get_error_name.ts | 6 +- ...register_transaction_duration_rule_type.ts | 1 + .../get_log_categories/index.ts | 18 +- .../get_container_id_from_signals.ts | 17 +- .../get_downstream_dependency_name.ts | 10 +- .../get_service_name_from_signals.ts | 14 +- .../get_metadata_for_dependency.ts | 16 +- .../dependencies/get_top_dependency_spans.ts | 43 ++-- .../get_error_group_main_statistics.ts | 61 ++++-- .../get_error_group_sample_ids.ts | 8 +- .../get_error_sample_details.ts | 95 ++++++++- .../apm/server/routes/errors/route.ts | 9 +- .../get_crash_group_main_statistics.ts | 66 ++++-- .../get_mobile_error_group_main_statistics.ts | 66 ++++-- .../get_derived_service_annotations.ts | 21 +- .../routes/services/get_service_agent.ts | 35 ++-- ...get_service_instance_container_metadata.ts | 23 ++- .../get_service_instance_metadata_details.ts | 44 +++- .../services/get_service_metadata_details.ts | 28 +-- .../services/get_service_metadata_icons.ts | 26 ++- .../routes/span_links/get_linked_children.ts | 46 +++-- .../routes/span_links/get_linked_parents.ts | 2 +- .../span_links/get_span_links_details.ts | 153 ++++++++------ .../traces/__snapshots__/queries.test.ts.snap | 19 +- .../server/routes/traces/get_trace_items.ts | 190 +++++++++++++----- .../traces/get_trace_samples_by_query.ts | 4 +- .../apm/server/routes/traces/route.ts | 13 +- .../__snapshots__/queries.test.ts.snap | 24 +++ .../routes/transactions/get_span/index.ts | 22 +- .../transactions/get_transaction/index.ts | 70 ++++++- .../get_transaction_by_name/index.ts | 28 ++- .../get_transaction_by_trace/index.ts | 53 ++++- .../transactions/trace_samples/index.ts | 23 ++- .../apm_data_access/server/utils.ts | 1 + .../utils/unflatten_known_fields.test.ts | 137 +++++++++++++ .../server/utils/unflatten_known_fields.ts | 168 ++++++++++++++++ .../apm_data_access/tsconfig.json | 4 +- 67 files changed, 1612 insertions(+), 385 deletions(-) create mode 100644 x-pack/packages/observability/observability_utils/object/unflatten_object.test.ts create mode 100644 x-pack/packages/observability/observability_utils/object/unflatten_object.ts create mode 100644 x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.test.ts create mode 100644 x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.ts diff --git a/packages/kbn-apm-types/src/es_fields/apm.ts b/packages/kbn-apm-types/src/es_fields/apm.ts index 6b0a68379f5d4..5d50833161979 100644 --- a/packages/kbn-apm-types/src/es_fields/apm.ts +++ b/packages/kbn-apm-types/src/es_fields/apm.ts @@ -7,7 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export const TIMESTAMP = 'timestamp.us'; +export const TIMESTAMP_US = 'timestamp.us'; +export const AT_TIMESTAMP = '@timestamp'; export const AGENT = 'agent'; export const AGENT_NAME = 'agent.name'; export const AGENT_VERSION = 'agent.version'; @@ -21,9 +22,11 @@ export const CLOUD_PROVIDER = 'cloud.provider'; export const CLOUD_REGION = 'cloud.region'; export const CLOUD_MACHINE_TYPE = 'cloud.machine.type'; export const CLOUD_ACCOUNT_ID = 'cloud.account.id'; +export const CLOUD_ACCOUNT_NAME = 'cloud.account.name'; export const CLOUD_INSTANCE_ID = 'cloud.instance.id'; export const CLOUD_INSTANCE_NAME = 'cloud.instance.name'; export const CLOUD_SERVICE_NAME = 'cloud.service.name'; +export const CLOUD_PROJECT_NAME = 'cloud.project.name'; export const EVENT_SUCCESS_COUNT = 'event.success_count'; @@ -48,10 +51,14 @@ export const USER_ID = 'user.id'; export const USER_AGENT_ORIGINAL = 'user_agent.original'; export const USER_AGENT_NAME = 'user_agent.name'; +export const OBSERVER_VERSION = 'observer.version'; +export const OBSERVER_VERSION_MAJOR = 'observer.version_major'; export const OBSERVER_HOSTNAME = 'observer.hostname'; export const OBSERVER_LISTENING = 'observer.listening'; export const PROCESSOR_EVENT = 'processor.event'; +export const PROCESSOR_NAME = 'processor.name'; +export const TRANSACTION_AGENT_MARKS = 'transaction.agent.marks'; export const TRANSACTION_DURATION = 'transaction.duration.us'; export const TRANSACTION_DURATION_HISTOGRAM = 'transaction.duration.histogram'; export const TRANSACTION_DURATION_SUMMARY = 'transaction.duration.summary'; @@ -95,6 +102,7 @@ export const SPAN_COMPOSITE_SUM = 'span.composite.sum.us'; export const SPAN_COMPOSITE_COMPRESSION_STRATEGY = 'span.composite.compression_strategy'; export const SPAN_SYNC = 'span.sync'; +export const SPAN_STACKTRACE = 'span.stacktrace'; // Parent ID for a transaction or span export const PARENT_ID = 'parent.id'; @@ -110,6 +118,7 @@ export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used i export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array export const ERROR_EXC_TYPE = 'error.exception.type'; export const ERROR_PAGE_URL = 'error.page.url'; +export const ERROR_STACK_TRACE = 'error.stack_trace'; export const ERROR_TYPE = 'error.type'; // METRICS @@ -153,6 +162,12 @@ export const CONTAINER_IMAGE = 'container.image.name'; export const KUBERNETES = 'kubernetes'; export const KUBERNETES_POD_NAME = 'kubernetes.pod.name'; export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; +export const KUBERNETES_NAMESPACE = 'kubernetes.namespace'; +export const KUBERNETES_NODE_NAME = 'kubernetes.node.name'; +export const KUBERNETES_CONTAINER_NAME = 'kubernetes.container.name'; +export const KUBERNETES_CONTAINER_ID = 'kubernetes.container.id'; +export const KUBERNETES_DEPLOYMENT_NAME = 'kubernetes.deployment.name'; +export const KUBERNETES_REPLICASET_NAME = 'kubernetes.replicaset.name'; export const FAAS_ID = 'faas.id'; export const FAAS_NAME = 'faas.name'; @@ -198,3 +213,7 @@ export const CLIENT_GEO_REGION_NAME = 'client.geo.region_name'; export const CHILD_ID = 'child.id'; export const LOG_LEVEL = 'log.level'; + +// Process +export const PROCESS_ARGS = 'process.args'; +export const PROCESS_PID = 'process.pid'; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts b/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts index b3a6066631346..14d26354e44ed 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts @@ -14,10 +14,10 @@ export interface APMBaseDoc { '@timestamp': string; agent: { name: string; - version: string; + version?: string; }; - parent?: { id: string }; // parent ID is not available on root transactions - trace?: { id: string }; + parent?: { id?: string }; // parent ID is not available on root transactions + trace?: { id?: string }; labels?: { [key: string]: string | number | boolean; }; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts index 7ee972faf7680..290be75091e18 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts @@ -10,26 +10,26 @@ export interface Cloud { availability_zone?: string; instance?: { - name: string; - id: string; + name?: string; + id?: string; }; machine?: { - type: string; + type?: string; }; project?: { - id: string; - name: string; + id?: string; + name?: string; }; provider?: string; region?: string; account?: { - id: string; - name: string; + id?: string; + name?: string; }; image?: { - id: string; + id?: string; }; service?: { - name: string; + name?: string; }; } diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts index 64dd497710b97..4c8d1ed4e52b4 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts @@ -9,5 +9,7 @@ export interface Container { id?: string | null; - image?: string | null; + image?: { + name?: string; + }; } diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts index 458731f690838..f3c62298ca8cb 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts @@ -8,7 +8,7 @@ */ export interface Http { - request?: { method: string; [key: string]: unknown }; - response?: { status_code: number; [key: string]: unknown }; + request?: { method?: string }; + response?: { status_code?: number }; version?: string; } diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts index 704d77f19f858..2a4f1465db9a5 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts @@ -8,7 +8,7 @@ */ export interface Kubernetes { - pod?: { uid?: string | null; [key: string]: unknown }; + pod?: { uid?: string | null; name?: string }; namespace?: string; replicaset?: { name?: string; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts index 067ecb9436ff9..7d286d4c3581e 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts @@ -13,6 +13,6 @@ export interface Observer { id?: string; name?: string; type?: string; - version: string; - version_major: number; + version?: string; + version_major?: number; } diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts index 6cc058ef75642..a18f3c5578eb5 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts @@ -9,5 +9,5 @@ // only for RUM agent: shared by error and transaction export interface Page { - url: string; + url?: string; } diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts index bcd9af08706ec..bd52784576dce 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts @@ -11,18 +11,18 @@ export interface Service { name: string; environment?: string; framework?: { - name: string; + name?: string; version?: string; }; node?: { name?: string; }; runtime?: { - name: string; - version: string; + name?: string; + version?: string; }; language?: { - name: string; + name?: string; version?: string; }; version?: string; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts index 3703763724f38..0f8cd3c814315 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts @@ -9,6 +9,6 @@ export interface Url { domain?: string; - full: string; + full?: string; original?: string; } diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts index 1c2235288a661..962ed1060b826 100644 --- a/packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts @@ -8,5 +8,5 @@ */ export interface User { - id: string; + id?: string; } diff --git a/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts b/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts index ea3ebf39555d2..e8734de141e83 100644 --- a/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts +++ b/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts @@ -14,5 +14,5 @@ export type { ElasticAgentName, OpenTelemetryAgentName, AgentName } from '@kbn/e export interface Agent { ephemeral_id?: string; name: AgentName; - version: string; + version?: string; } diff --git a/x-pack/packages/observability/observability_utils/object/flatten_object.test.ts b/x-pack/packages/observability/observability_utils/object/flatten_object.test.ts index deb7ed998c478..13a8174f4f1cf 100644 --- a/x-pack/packages/observability/observability_utils/object/flatten_object.test.ts +++ b/x-pack/packages/observability/observability_utils/object/flatten_object.test.ts @@ -21,6 +21,18 @@ describe('flattenObject', () => { }); }); + it('flattens arrays', () => { + expect( + flattenObject({ + child: { + id: [1, 2], + }, + }) + ).toEqual({ + 'child.id': [1, 2], + }); + }); + it('does not flatten arrays', () => { expect( flattenObject({ diff --git a/x-pack/packages/observability/observability_utils/object/unflatten_object.test.ts b/x-pack/packages/observability/observability_utils/object/unflatten_object.test.ts new file mode 100644 index 0000000000000..22cee17bb1a64 --- /dev/null +++ b/x-pack/packages/observability/observability_utils/object/unflatten_object.test.ts @@ -0,0 +1,40 @@ +/* + * 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 { unflattenObject } from './unflatten_object'; + +describe('unflattenObject', () => { + it('unflattens deeply nested objects', () => { + expect(unflattenObject({ 'first.second.third': 'third' })).toEqual({ + first: { + second: { + third: 'third', + }, + }, + }); + }); + + it('does not unflatten arrays', () => { + expect( + unflattenObject({ + simpleArray: ['0', '1', '2'], + complexArray: [{ one: 'one', two: 'two', three: 'three' }], + 'nested.array': [0, 1, 2], + 'complex.nested': [{ one: 'one', two: 'two', 'first.second': 'foo', 'first.third': 'bar' }], + }) + ).toEqual({ + simpleArray: ['0', '1', '2'], + complexArray: [{ one: 'one', two: 'two', three: 'three' }], + nested: { + array: [0, 1, 2], + }, + complex: { + nested: [{ one: 'one', two: 'two', first: { second: 'foo', third: 'bar' } }], + }, + }); + }); +}); diff --git a/x-pack/packages/observability/observability_utils/object/unflatten_object.ts b/x-pack/packages/observability/observability_utils/object/unflatten_object.ts new file mode 100644 index 0000000000000..142ea2eea6461 --- /dev/null +++ b/x-pack/packages/observability/observability_utils/object/unflatten_object.ts @@ -0,0 +1,28 @@ +/* + * 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 { set } from '@kbn/safer-lodash-set'; + +export function unflattenObject(source: Record, target: Record = {}) { + // eslint-disable-next-line guard-for-in + for (const key in source) { + const val = source[key as keyof typeof source]; + + if (Array.isArray(val)) { + const unflattenedArray = val.map((item) => { + if (item && typeof item === 'object' && !Array.isArray(item)) { + return unflattenObject(item); + } + return item; + }); + set(target, key, unflattenedArray); + } else { + set(target, key, val); + } + } + return target; +} diff --git a/x-pack/packages/observability/observability_utils/tsconfig.json b/x-pack/packages/observability/observability_utils/tsconfig.json index 2ed47d10cfad9..b3f1a4a21c4e7 100644 --- a/x-pack/packages/observability/observability_utils/tsconfig.json +++ b/x-pack/packages/observability/observability_utils/tsconfig.json @@ -21,5 +21,6 @@ "@kbn/es-types", "@kbn/apm-utils", "@kbn/es-query", + "@kbn/safer-lodash-set", ] } diff --git a/x-pack/plugins/observability_solution/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap b/x-pack/plugins/observability_solution/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap index 6fa3e146a423d..88d00196e074b 100644 --- a/x-pack/plugins/observability_solution/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap +++ b/x-pack/plugins/observability_solution/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap @@ -37,6 +37,8 @@ Object { exports[`Error CLOUD_ACCOUNT_ID 1`] = `undefined`; +exports[`Error CLOUD_ACCOUNT_NAME 1`] = `undefined`; + exports[`Error CLOUD_AVAILABILITY_ZONE 1`] = `"europe-west1-c"`; exports[`Error CLOUD_INSTANCE_ID 1`] = `undefined`; @@ -45,6 +47,8 @@ exports[`Error CLOUD_INSTANCE_NAME 1`] = `undefined`; exports[`Error CLOUD_MACHINE_TYPE 1`] = `undefined`; +exports[`Error CLOUD_PROJECT_NAME 1`] = `undefined`; + exports[`Error CLOUD_PROVIDER 1`] = `"gcp"`; exports[`Error CLOUD_REGION 1`] = `"europe-west1"`; @@ -94,6 +98,8 @@ exports[`Error ERROR_LOG_MESSAGE 1`] = `undefined`; exports[`Error ERROR_PAGE_URL 1`] = `undefined`; +exports[`Error ERROR_STACK_TRACE 1`] = `undefined`; + exports[`Error ERROR_TYPE 1`] = `undefined`; exports[`Error EVENT_NAME 1`] = `undefined`; @@ -140,6 +146,8 @@ exports[`Error INDEX 1`] = `undefined`; exports[`Error KUBERNETES 1`] = `undefined`; +exports[`Error KUBERNETES_CONTAINER_ID 1`] = `undefined`; + exports[`Error KUBERNETES_CONTAINER_NAME 1`] = `undefined`; exports[`Error KUBERNETES_DEPLOYMENT 1`] = `undefined`; @@ -150,6 +158,8 @@ exports[`Error KUBERNETES_NAMESPACE 1`] = `undefined`; exports[`Error KUBERNETES_NAMESPACE_NAME 1`] = `undefined`; +exports[`Error KUBERNETES_NODE_NAME 1`] = `undefined`; + exports[`Error KUBERNETES_POD_NAME 1`] = `undefined`; exports[`Error KUBERNETES_POD_UID 1`] = `undefined`; @@ -228,10 +238,20 @@ exports[`Error OBSERVER_HOSTNAME 1`] = `undefined`; exports[`Error OBSERVER_LISTENING 1`] = `undefined`; +exports[`Error OBSERVER_VERSION 1`] = `"whatever"`; + +exports[`Error OBSERVER_VERSION_MAJOR 1`] = `8`; + exports[`Error PARENT_ID 1`] = `"parentId"`; +exports[`Error PROCESS_ARGS 1`] = `undefined`; + +exports[`Error PROCESS_PID 1`] = `undefined`; + exports[`Error PROCESSOR_EVENT 1`] = `"error"`; +exports[`Error PROCESSOR_NAME 1`] = `"error"`; + exports[`Error SERVICE 1`] = ` Object { "language": Object { @@ -296,6 +316,8 @@ exports[`Error SPAN_NAME 1`] = `undefined`; exports[`Error SPAN_SELF_TIME_SUM 1`] = `undefined`; +exports[`Error SPAN_STACKTRACE 1`] = `undefined`; + exports[`Error SPAN_SUBTYPE 1`] = `undefined`; exports[`Error SPAN_SYNC 1`] = `undefined`; @@ -304,10 +326,12 @@ exports[`Error SPAN_TYPE 1`] = `undefined`; exports[`Error TIER 1`] = `undefined`; -exports[`Error TIMESTAMP 1`] = `1337`; +exports[`Error TIMESTAMP_US 1`] = `1337`; exports[`Error TRACE_ID 1`] = `"trace id"`; +exports[`Error TRANSACTION_AGENT_MARKS 1`] = `undefined`; + exports[`Error TRANSACTION_DURATION 1`] = `undefined`; exports[`Error TRANSACTION_DURATION_HISTOGRAM 1`] = `undefined`; @@ -385,6 +409,8 @@ Object { exports[`Span CLOUD_ACCOUNT_ID 1`] = `undefined`; +exports[`Span CLOUD_ACCOUNT_NAME 1`] = `undefined`; + exports[`Span CLOUD_AVAILABILITY_ZONE 1`] = `"europe-west1-c"`; exports[`Span CLOUD_INSTANCE_ID 1`] = `undefined`; @@ -393,6 +419,8 @@ exports[`Span CLOUD_INSTANCE_NAME 1`] = `undefined`; exports[`Span CLOUD_MACHINE_TYPE 1`] = `undefined`; +exports[`Span CLOUD_PROJECT_NAME 1`] = `undefined`; + exports[`Span CLOUD_PROVIDER 1`] = `"gcp"`; exports[`Span CLOUD_REGION 1`] = `"europe-west1"`; @@ -433,6 +461,8 @@ exports[`Span ERROR_LOG_MESSAGE 1`] = `undefined`; exports[`Span ERROR_PAGE_URL 1`] = `undefined`; +exports[`Span ERROR_STACK_TRACE 1`] = `undefined`; + exports[`Span ERROR_TYPE 1`] = `undefined`; exports[`Span EVENT_NAME 1`] = `undefined`; @@ -475,6 +505,8 @@ exports[`Span INDEX 1`] = `undefined`; exports[`Span KUBERNETES 1`] = `undefined`; +exports[`Span KUBERNETES_CONTAINER_ID 1`] = `undefined`; + exports[`Span KUBERNETES_CONTAINER_NAME 1`] = `undefined`; exports[`Span KUBERNETES_DEPLOYMENT 1`] = `undefined`; @@ -485,6 +517,8 @@ exports[`Span KUBERNETES_NAMESPACE 1`] = `undefined`; exports[`Span KUBERNETES_NAMESPACE_NAME 1`] = `undefined`; +exports[`Span KUBERNETES_NODE_NAME 1`] = `undefined`; + exports[`Span KUBERNETES_POD_NAME 1`] = `undefined`; exports[`Span KUBERNETES_POD_UID 1`] = `undefined`; @@ -563,10 +597,20 @@ exports[`Span OBSERVER_HOSTNAME 1`] = `undefined`; exports[`Span OBSERVER_LISTENING 1`] = `undefined`; +exports[`Span OBSERVER_VERSION 1`] = `"whatever"`; + +exports[`Span OBSERVER_VERSION_MAJOR 1`] = `8`; + exports[`Span PARENT_ID 1`] = `"parentId"`; +exports[`Span PROCESS_ARGS 1`] = `undefined`; + +exports[`Span PROCESS_PID 1`] = `undefined`; + exports[`Span PROCESSOR_EVENT 1`] = `"span"`; +exports[`Span PROCESSOR_NAME 1`] = `"transaction"`; + exports[`Span SERVICE 1`] = ` Object { "name": "service name", @@ -627,6 +671,8 @@ exports[`Span SPAN_NAME 1`] = `"span name"`; exports[`Span SPAN_SELF_TIME_SUM 1`] = `undefined`; +exports[`Span SPAN_STACKTRACE 1`] = `undefined`; + exports[`Span SPAN_SUBTYPE 1`] = `"my subtype"`; exports[`Span SPAN_SYNC 1`] = `false`; @@ -635,10 +681,12 @@ exports[`Span SPAN_TYPE 1`] = `"span type"`; exports[`Span TIER 1`] = `undefined`; -exports[`Span TIMESTAMP 1`] = `1337`; +exports[`Span TIMESTAMP_US 1`] = `1337`; exports[`Span TRACE_ID 1`] = `"trace id"`; +exports[`Span TRANSACTION_AGENT_MARKS 1`] = `undefined`; + exports[`Span TRANSACTION_DURATION 1`] = `undefined`; exports[`Span TRANSACTION_DURATION_HISTOGRAM 1`] = `undefined`; @@ -716,6 +764,8 @@ Object { exports[`Transaction CLOUD_ACCOUNT_ID 1`] = `undefined`; +exports[`Transaction CLOUD_ACCOUNT_NAME 1`] = `undefined`; + exports[`Transaction CLOUD_AVAILABILITY_ZONE 1`] = `"europe-west1-c"`; exports[`Transaction CLOUD_INSTANCE_ID 1`] = `undefined`; @@ -724,6 +774,8 @@ exports[`Transaction CLOUD_INSTANCE_NAME 1`] = `undefined`; exports[`Transaction CLOUD_MACHINE_TYPE 1`] = `undefined`; +exports[`Transaction CLOUD_PROJECT_NAME 1`] = `undefined`; + exports[`Transaction CLOUD_PROVIDER 1`] = `"gcp"`; exports[`Transaction CLOUD_REGION 1`] = `"europe-west1"`; @@ -768,6 +820,8 @@ exports[`Transaction ERROR_LOG_MESSAGE 1`] = `undefined`; exports[`Transaction ERROR_PAGE_URL 1`] = `undefined`; +exports[`Transaction ERROR_STACK_TRACE 1`] = `undefined`; + exports[`Transaction ERROR_TYPE 1`] = `undefined`; exports[`Transaction EVENT_NAME 1`] = `undefined`; @@ -820,6 +874,8 @@ Object { } `; +exports[`Transaction KUBERNETES_CONTAINER_ID 1`] = `undefined`; + exports[`Transaction KUBERNETES_CONTAINER_NAME 1`] = `undefined`; exports[`Transaction KUBERNETES_DEPLOYMENT 1`] = `undefined`; @@ -830,6 +886,8 @@ exports[`Transaction KUBERNETES_NAMESPACE 1`] = `undefined`; exports[`Transaction KUBERNETES_NAMESPACE_NAME 1`] = `undefined`; +exports[`Transaction KUBERNETES_NODE_NAME 1`] = `undefined`; + exports[`Transaction KUBERNETES_POD_NAME 1`] = `undefined`; exports[`Transaction KUBERNETES_POD_UID 1`] = `"pod1234567890abcdef"`; @@ -908,10 +966,20 @@ exports[`Transaction OBSERVER_HOSTNAME 1`] = `undefined`; exports[`Transaction OBSERVER_LISTENING 1`] = `undefined`; +exports[`Transaction OBSERVER_VERSION 1`] = `"whatever"`; + +exports[`Transaction OBSERVER_VERSION_MAJOR 1`] = `8`; + exports[`Transaction PARENT_ID 1`] = `"parentId"`; +exports[`Transaction PROCESS_ARGS 1`] = `undefined`; + +exports[`Transaction PROCESS_PID 1`] = `undefined`; + exports[`Transaction PROCESSOR_EVENT 1`] = `"transaction"`; +exports[`Transaction PROCESSOR_NAME 1`] = `"transaction"`; + exports[`Transaction SERVICE 1`] = ` Object { "language": Object { @@ -976,6 +1044,8 @@ exports[`Transaction SPAN_NAME 1`] = `undefined`; exports[`Transaction SPAN_SELF_TIME_SUM 1`] = `undefined`; +exports[`Transaction SPAN_STACKTRACE 1`] = `undefined`; + exports[`Transaction SPAN_SUBTYPE 1`] = `undefined`; exports[`Transaction SPAN_SYNC 1`] = `undefined`; @@ -984,10 +1054,12 @@ exports[`Transaction SPAN_TYPE 1`] = `undefined`; exports[`Transaction TIER 1`] = `undefined`; -exports[`Transaction TIMESTAMP 1`] = `1337`; +exports[`Transaction TIMESTAMP_US 1`] = `1337`; exports[`Transaction TRACE_ID 1`] = `"trace id"`; +exports[`Transaction TRANSACTION_AGENT_MARKS 1`] = `undefined`; + exports[`Transaction TRANSACTION_DURATION 1`] = `1337`; exports[`Transaction TRANSACTION_DURATION_HISTOGRAM 1`] = `undefined`; diff --git a/x-pack/plugins/observability_solution/apm/common/es_fields/es_fields.test.ts b/x-pack/plugins/observability_solution/apm/common/es_fields/es_fields.test.ts index f33fddd430e8d..12537d35afefe 100644 --- a/x-pack/plugins/observability_solution/apm/common/es_fields/es_fields.test.ts +++ b/x-pack/plugins/observability_solution/apm/common/es_fields/es_fields.test.ts @@ -10,12 +10,13 @@ import { AllowUnknownProperties } from '../../typings/common'; import { APMError } from '../../typings/es_schemas/ui/apm_error'; import { Span } from '../../typings/es_schemas/ui/span'; import { Transaction } from '../../typings/es_schemas/ui/transaction'; -import * as apmFieldnames from './apm'; -import * as infraMetricsFieldnames from './infra_metrics'; +import * as allApmFieldNames from './apm'; +import * as infraMetricsFieldNames from './infra_metrics'; +const { AT_TIMESTAMP, ...apmFieldNames } = allApmFieldNames; const fieldnames = { - ...apmFieldnames, - ...infraMetricsFieldnames, + ...apmFieldNames, + ...infraMetricsFieldNames, }; describe('Transaction', () => { diff --git a/x-pack/plugins/observability_solution/apm/common/service_metadata.ts b/x-pack/plugins/observability_solution/apm/common/service_metadata.ts index 0ccede67741b7..4136ea361392e 100644 --- a/x-pack/plugins/observability_solution/apm/common/service_metadata.ts +++ b/x-pack/plugins/observability_solution/apm/common/service_metadata.ts @@ -4,5 +4,63 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { + CLOUD_AVAILABILITY_ZONE, + CLOUD_INSTANCE_ID, + CLOUD_INSTANCE_NAME, + CLOUD_MACHINE_TYPE, + CLOUD_PROVIDER, + CONTAINER_ID, + HOST_NAME, + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_DEPLOYMENT_NAME, + KUBERNETES_POD_NAME, + KUBERNETES_POD_UID, + KUBERNETES_REPLICASET_NAME, + SERVICE_NODE_NAME, + SERVICE_RUNTIME_NAME, + SERVICE_RUNTIME_VERSION, + SERVICE_VERSION, +} from './es_fields/apm'; +import { asMutableArray } from './utils/as_mutable_array'; + +export const SERVICE_METADATA_SERVICE_KEYS = asMutableArray([ + SERVICE_NODE_NAME, + SERVICE_VERSION, + SERVICE_RUNTIME_NAME, + SERVICE_RUNTIME_VERSION, +] as const); + +export const SERVICE_METADATA_CONTAINER_KEYS = asMutableArray([ + CONTAINER_ID, + HOST_NAME, + KUBERNETES_POD_UID, + KUBERNETES_POD_NAME, +] as const); + +export const SERVICE_METADATA_INFRA_METRICS_KEYS = asMutableArray([ + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_REPLICASET_NAME, + KUBERNETES_DEPLOYMENT_NAME, +] as const); + +export const SERVICE_METADATA_CLOUD_KEYS = asMutableArray([ + CLOUD_AVAILABILITY_ZONE, + CLOUD_INSTANCE_ID, + CLOUD_INSTANCE_NAME, + CLOUD_MACHINE_TYPE, + CLOUD_PROVIDER, +] as const); + +export const SERVICE_METADATA_KUBERNETES_KEYS = asMutableArray([ + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_DEPLOYMENT_NAME, + KUBERNETES_POD_NAME, + KUBERNETES_POD_UID, + KUBERNETES_REPLICASET_NAME, +] as const); export type ContainerType = 'Kubernetes' | 'Docker' | undefined; diff --git a/x-pack/plugins/observability_solution/apm/common/waterfall/typings.ts b/x-pack/plugins/observability_solution/apm/common/waterfall/typings.ts index 49f282473e9dd..2fd0be94a5c5f 100644 --- a/x-pack/plugins/observability_solution/apm/common/waterfall/typings.ts +++ b/x-pack/plugins/observability_solution/apm/common/waterfall/typings.ts @@ -64,16 +64,17 @@ export interface WaterfallSpan { links?: SpanLink[]; }; transaction?: { - id: string; + id?: string; }; child?: { id: string[] }; } export interface WaterfallError { timestamp: TimestampUs; - trace?: { id: string }; - transaction?: { id: string }; - parent?: { id: string }; + trace?: { id?: string }; + transaction?: { id?: string }; + parent?: { id?: string }; + span?: { id?: string }; error: { id: string; log?: { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx index 2e91865083b8c..20d5521b43ebf 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx @@ -8,9 +8,9 @@ import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { Message } from '@kbn/observability-ai-assistant-plugin/public'; import React, { useMemo, useState } from 'react'; +import { AT_TIMESTAMP } from '@kbn/apm-types'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { ErrorSampleDetailTabContent } from './error_sample_detail'; import { exceptionStacktraceTab, logStacktraceTab } from './error_tabs'; @@ -18,8 +18,26 @@ export function ErrorSampleContextualInsight({ error, transaction, }: { - error: APMError; - transaction?: Transaction; + error: { + [AT_TIMESTAMP]: string; + error: Pick; + service: { + name: string; + environment?: string; + language?: { + name?: string; + }; + runtime?: { + name?: string; + version?: string; + }; + }; + }; + transaction?: { + transaction: { + name: string; + }; + }; }) { const { observabilityAIAssistant } = useApmPluginContext(); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx index a38c4dfc96f63..2edb2c1a3fea6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx @@ -29,14 +29,14 @@ import { first } from 'lodash'; import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; -import { ERROR_GROUP_ID } from '../../../../../common/es_fields/apm'; +import { AT_TIMESTAMP, ERROR_GROUP_ID } from '../../../../../common/es_fields/apm'; import { TraceSearchType } from '../../../../../common/trace_explorer'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; import { useApmRouter } from '../../../../hooks/use_apm_router'; -import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, isPending, isSuccess } from '../../../../hooks/use_fetcher'; import { useTraceExplorerEnabledSetting } from '../../../../hooks/use_trace_explorer_enabled_setting'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { TransactionDetailLink } from '../../../shared/links/apm/transaction_detail_link'; @@ -111,8 +111,7 @@ export function ErrorSampleDetails({ const loadingErrorData = isPending(errorFetchStatus); const isLoading = loadingErrorSamplesData || loadingErrorData; - const isSucceded = - errorSamplesFetchStatus === FETCH_STATUS.SUCCESS && errorFetchStatus === FETCH_STATUS.SUCCESS; + const isSucceeded = isSuccess(errorSamplesFetchStatus) && isSuccess(errorFetchStatus); useEffect(() => { setSampleActivePage(0); @@ -137,7 +136,7 @@ export function ErrorSampleDetails({ }); }, [error, transaction, uiActions]); - if (!error && errorSampleIds?.length === 0 && isSucceded) { + if (!error && errorSampleIds?.length === 0 && isSucceeded) { return ( ; + }; currentTab: ErrorTab; }) { const codeLanguage = error?.service.language?.name; const exceptions = error?.error.exception || []; const logStackframes = error?.error.log?.stacktrace; const isPlaintextException = - !!error?.error.stack_trace && exceptions.length === 1 && !exceptions[0].stacktrace; + !!error.error.stack_trace && exceptions.length === 1 && !exceptions[0].stacktrace; switch (currentTab.key) { case ErrorTabKey.LogStackTrace: return ; @@ -363,7 +370,7 @@ export function ErrorSampleDetailTabContent({ return isPlaintextException ? ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_tabs.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_tabs.tsx index 893e842513c8f..86b69eb480b3f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_tabs.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_tabs.tsx @@ -41,7 +41,7 @@ export const metadataTab: ErrorTab = { }), }; -export function getTabs(error: APMError) { +export function getTabs(error: { error: { log?: APMError['error']['log'] } }) { const hasLogStacktrace = !isEmpty(error?.error.log?.stacktrace); return [...(hasLogStacktrace ? [logStacktraceTab] : []), exceptionStacktraceTab, metadataTab]; } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/sample_summary.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/sample_summary.tsx index af05e8766994c..c7acbfee7e45e 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/sample_summary.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/sample_summary.tsx @@ -18,7 +18,9 @@ const Label = euiStyled.div` `; interface Props { - error: APMError; + error: { + error: Pick; + }; } export function SampleSummary({ error }: Props) { const logMessage = error.error.log?.message; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/get_redirect_to_transaction_detail_page_url.ts b/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/get_redirect_to_transaction_detail_page_url.ts index a3467d7272ff5..acd9e9445ad8f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/get_redirect_to_transaction_detail_page_url.ts +++ b/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/get_redirect_to_transaction_detail_page_url.ts @@ -6,7 +6,7 @@ */ import { format } from 'url'; -import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; +import type { TransactionDetailRedirectInfo } from '../../../../server/routes/transactions/get_transaction_by_trace'; export const getRedirectToTransactionDetailPageUrl = ({ transaction, @@ -14,7 +14,7 @@ export const getRedirectToTransactionDetailPageUrl = ({ rangeTo, waterfallItemId, }: { - transaction: Transaction; + transaction: TransactionDetailRedirectInfo; rangeFrom?: string; rangeTo?: string; waterfallItemId?: string; diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/links/discover_links/discover_error_link.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/links/discover_links/discover_error_link.tsx index 2958d2af7d68f..a32c01f3b15e5 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/links/discover_links/discover_error_link.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/links/discover_links/discover_error_link.tsx @@ -7,10 +7,18 @@ import React, { ReactNode } from 'react'; import { ERROR_GROUP_ID, SERVICE_NAME } from '../../../../../common/es_fields/apm'; -import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { DiscoverLink } from './discover_link'; -function getDiscoverQuery(error: APMError, kuery?: string) { +interface ErrorForDiscoverQuery { + service: { + name: string; + }; + error: { + grouping_key: string; + }; +} + +function getDiscoverQuery(error: ErrorForDiscoverQuery, kuery?: string) { const serviceName = error.service.name; const groupId = error.error.grouping_key; let query = `${SERVICE_NAME}:"${serviceName}" AND ${ERROR_GROUP_ID}:"${groupId}"`; @@ -36,7 +44,7 @@ function DiscoverErrorLink({ children, }: { children?: ReactNode; - readonly error: APMError; + readonly error: ErrorForDiscoverQuery; readonly kuery?: string; }) { return ; diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/metadata_table/error_metadata/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/metadata_table/error_metadata/index.tsx index ae688f8917602..dab585180fce9 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/metadata_table/error_metadata/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/metadata_table/error_metadata/index.tsx @@ -7,13 +7,16 @@ import React, { useMemo } from 'react'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; +import { APMError, AT_TIMESTAMP } from '@kbn/apm-types'; import { getSectionsFromFields } from '../helper'; import { MetadataTable } from '..'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; interface Props { - error: APMError; + error: { + [AT_TIMESTAMP]: string; + error: Pick; + }; } export function ErrorMetadata({ error }: Props) { @@ -26,8 +29,8 @@ export function ErrorMetadata({ error }: Props) { id: error.error.id, }, query: { - start: error['@timestamp'], - end: error['@timestamp'], + start: error[AT_TIMESTAMP], + end: error[AT_TIMESTAMP], }, }, }); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts b/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts index 319582f61b664..a2b6809f855e7 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts @@ -8,7 +8,7 @@ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; import { tasks } from './tasks'; -import { SERVICE_NAME, SERVICE_ENVIRONMENT } from '../../../../common/es_fields/apm'; +import { SERVICE_NAME, SERVICE_ENVIRONMENT, AT_TIMESTAMP } from '../../../../common/es_fields/apm'; import { IndicesStatsResponse } from '../telemetry_client'; describe('data telemetry collection tasks', () => { @@ -101,7 +101,7 @@ describe('data telemetry collection tasks', () => { // a fixed date range. .mockReturnValueOnce({ hits: { - hits: [{ _source: { '@timestamp': new Date().toISOString() } }], + hits: [{ fields: { [AT_TIMESTAMP]: [new Date().toISOString()] } }], }, total: { value: 1, @@ -314,7 +314,7 @@ describe('data telemetry collection tasks', () => { ? { hits: { total: { value: 1 } } } : { hits: { - hits: [{ _source: { '@timestamp': 1 } }], + hits: [{ fields: { [AT_TIMESTAMP]: [1] } }], }, } ); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index db6a6a918177a..1347cbb4e3641 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -11,11 +11,13 @@ import { createHash } from 'crypto'; import { flatten, merge, pickBy, sortBy, sum, uniq } from 'lodash'; import { SavedObjectsClient } from '@kbn/core/server'; import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; import { AGENT_NAMES, RUM_AGENT_NAMES } from '../../../../common/agent_name'; import { AGENT_ACTIVATION_METHOD, AGENT_NAME, AGENT_VERSION, + AT_TIMESTAMP, CLIENT_GEO_COUNTRY_ISO_CODE, CLOUD_AVAILABILITY_ZONE, CLOUD_PROVIDER, @@ -29,6 +31,7 @@ import { METRICSET_INTERVAL, METRICSET_NAME, OBSERVER_HOSTNAME, + OBSERVER_VERSION, PARENT_ID, PROCESSOR_EVENT, SERVICE_ENVIRONMENT, @@ -54,10 +57,7 @@ import { SavedServiceGroup, } from '../../../../common/service_groups'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; -import { APMError } from '../../../../typings/es_schemas/ui/apm_error'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; -import { Span } from '../../../../typings/es_schemas/ui/span'; -import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { APMDataTelemetry, APMPerService, @@ -193,17 +193,19 @@ export const tasks: TelemetryTask[] = [ size: 1, track_total_hits: false, sort: { - '@timestamp': 'desc' as const, + [AT_TIMESTAMP]: 'desc' as const, }, + fields: [AT_TIMESTAMP], }, }) - ).hits.hits[0] as { _source: { '@timestamp': string } }; + ).hits.hits[0]; if (!lastTransaction) { return {}; } - const end = new Date(lastTransaction._source['@timestamp']).getTime() - 5 * 60 * 1000; + const end = + new Date(lastTransaction.fields[AT_TIMESTAMP]![0] as string).getTime() - 5 * 60 * 1000; const start = end - 60 * 1000; @@ -512,16 +514,16 @@ export const tasks: TelemetryTask[] = [ }, }, sort: { - '@timestamp': 'asc', + [AT_TIMESTAMP]: 'asc', }, - _source: ['@timestamp'], + fields: [AT_TIMESTAMP], }, }) : null; - const event = retainmentResponse?.hits.hits[0]?._source as + const event = retainmentResponse?.hits.hits[0]?.fields as | { - '@timestamp': number; + [AT_TIMESTAMP]: number[]; } | undefined; @@ -535,7 +537,7 @@ export const tasks: TelemetryTask[] = [ ? { retainment: { [processorEvent]: { - ms: new Date().getTime() - new Date(event['@timestamp']).getTime(), + ms: new Date().getTime() - new Date(event[AT_TIMESTAMP][0]).getTime(), }, }, } @@ -690,16 +692,16 @@ export const tasks: TelemetryTask[] = [ sort: { '@timestamp': 'desc', }, + fields: asMutableArray([OBSERVER_VERSION] as const), }, }); - const hit = response.hits.hits[0]?._source as Pick; - - if (!hit || !hit.observer?.version) { + const event = unflattenKnownApmEventFields(response.hits.hits[0]?.fields); + if (!event || !event.observer?.version) { return {}; } - const [major, minor, patch] = hit.observer.version.split('.').map((part) => Number(part)); + const [major, minor, patch] = event.observer.version.split('.').map((part) => Number(part)); return { version: { diff --git a/x-pack/plugins/observability_solution/apm/server/lib/connections/get_connection_stats/get_destination_map.ts b/x-pack/plugins/observability_solution/apm/server/lib/connections/get_connection_stats/get_destination_map.ts index 4ab2e753832a4..cbcad6dea5baf 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/connections/get_connection_stats/get_destination_map.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/connections/get_connection_stats/get_destination_map.ts @@ -186,7 +186,6 @@ export const getDestinationMap = ({ }, size: destinationsBySpanId.size, fields: asMutableArray([SERVICE_NAME, SERVICE_ENVIRONMENT, AGENT_NAME, PARENT_ID] as const), - _source: false, }, }); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_error_name.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_error_name.ts index 88d0040f70fc9..5d4977a73b42f 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_error_name.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_error_name.ts @@ -9,6 +9,10 @@ import { NOT_AVAILABLE_LABEL } from '../../../common/i18n'; import { Maybe } from '../../../typings/common'; import { APMError } from '../../../typings/es_schemas/ui/apm_error'; -export function getErrorName({ error }: { error: Maybe }): string { +export function getErrorName({ + error, +}: { + error: Maybe> & { log?: { message?: string } }; +}): string { return error?.log?.message || error?.exception?.[0]?.message || NOT_AVAILABLE_LABEL; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index 96ddbe15c4287..dfc32ec9eb54e 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -184,6 +184,7 @@ export function registerTransactionDurationRuleType({ body: { track_total_hits: false, size: 0, + _source: false as const, query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts index 5f36325031ccb..7072639f8526e 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts @@ -8,6 +8,9 @@ import datemath from '@elastic/datemath'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { maybe } from '../../../../common/utils/maybe'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { flattenObject, KeyValuePair } from '../../../../common/utils/flatten_object'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { PROCESSOR_EVENT, TRACE_ID } from '../../../../common/es_fields/apm'; @@ -86,6 +89,7 @@ export async function getLogCategories({ const rawSamplingProbability = Math.min(100_000 / totalDocCount, 1); const samplingProbability = rawSamplingProbability < 0.5 ? rawSamplingProbability : 1; + const fields = asMutableArray(['message', TRACE_ID] as const); const categorizedLogsRes = await search({ index, size: 1, @@ -108,7 +112,7 @@ export async function getLogCategories({ top_hits: { sort: { '@timestamp': 'desc' as const }, size: 1, - _source: ['message', TRACE_ID], + fields, }, }, }, @@ -120,9 +124,11 @@ export async function getLogCategories({ const promises = categorizedLogsRes.aggregations?.sampling.categories?.buckets.map( async ({ doc_count: docCount, key, sample }) => { - const hit = sample.hits.hits[0]._source as { message: string; trace?: { id: string } }; - const sampleMessage = hit?.message; - const sampleTraceId = hit?.trace?.id; + const hit = sample.hits.hits[0]; + const event = unflattenKnownApmEventFields(hit?.fields); + + const sampleMessage = event.message as string; + const sampleTraceId = event.trace?.id; const errorCategory = key as string; if (!sampleTraceId) { @@ -140,7 +146,9 @@ export async function getLogCategories({ } ); - const sampleDoc = categorizedLogsRes.hits.hits?.[0]?._source as Record; + const event = unflattenKnownApmEventFields(maybe(categorizedLogsRes.hits.hits[0])?.fields); + + const sampleDoc = event as Record; return { logCategories: await Promise.all(promises ?? []), diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts index e7f3ace07e2a1..93c55cf1a9a30 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts @@ -13,6 +13,10 @@ import moment from 'moment'; import { ESSearchRequest } from '@kbn/es-types'; import { alertDetailsContextRt } from '@kbn/observability-plugin/server/services'; import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; +import { CONTAINER_ID } from '@kbn/apm-types'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { maybe } from '../../../../common/utils/maybe'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { ApmDocumentType } from '../../../../common/document_type'; import { APMEventClient, @@ -79,13 +83,17 @@ async function getContainerIdFromLogs({ esClient: ElasticsearchClient; logSourcesService: LogSourcesService; }) { + const requiredFields = asMutableArray([CONTAINER_ID] as const); const index = await logSourcesService.getFlattenedLogSources(); const res = await typedSearch<{ container: { id: string } }, any>(esClient, { index, ...params, + fields: requiredFields, }); - return res.hits.hits[0]?._source?.container?.id; + const event = unflattenKnownApmEventFields(maybe(res.hits.hits[0])?.fields, requiredFields); + + return event?.container.id; } async function getContainerIdFromTraces({ @@ -95,6 +103,7 @@ async function getContainerIdFromTraces({ params: APMEventESSearchRequest['body']; apmEventClient: APMEventClient; }) { + const requiredFields = asMutableArray([CONTAINER_ID] as const); const res = await apmEventClient.search('get_container_id_from_traces', { apm: { sources: [ @@ -104,8 +113,10 @@ async function getContainerIdFromTraces({ }, ], }, - body: params, + body: { ...params, fields: requiredFields }, }); - return res.hits.hits[0]?._source.container?.id; + const event = unflattenKnownApmEventFields(maybe(res.hits.hits[0])?.fields, requiredFields); + + return event?.container.id; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_downstream_dependency_name.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_downstream_dependency_name.ts index a5b75f76ff237..d957372285b02 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_downstream_dependency_name.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_downstream_dependency_name.ts @@ -6,6 +6,9 @@ */ import { rangeQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { maybe } from '../../../../common/utils/maybe'; import { ApmDocumentType } from '../../../../common/document_type'; import { termQuery } from '../../../../common/utils/term_query'; import { @@ -27,6 +30,7 @@ export async function getDownstreamServiceResource({ end: number; apmEventClient: APMEventClient; }) { + const requiredFields = asMutableArray([SPAN_DESTINATION_SERVICE_RESOURCE] as const); const response = await apmEventClient.search('get_error_group_main_statistics', { apm: { sources: [ @@ -50,9 +54,11 @@ export async function getDownstreamServiceResource({ ], }, }, + fields: requiredFields, }, }); - const hit = response.hits.hits[0]; - return hit?._source?.span.destination?.service.resource; + const event = unflattenKnownApmEventFields(maybe(response.hits.hits[0])?.fields, requiredFields); + + return event?.span.destination.service.resource; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts index 0168431d0ac4e..bd966c500d1bc 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts @@ -12,6 +12,10 @@ import moment from 'moment'; import { ESSearchRequest } from '@kbn/es-types'; import { alertDetailsContextRt } from '@kbn/observability-plugin/server/services'; import type { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { SERVICE_NAME } from '@kbn/apm-types'; +import { maybe } from '../../../../common/utils/maybe'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { ApmDocumentType } from '../../../../common/document_type'; import { APMEventClient, @@ -102,6 +106,7 @@ async function getServiceNameFromTraces({ params: APMEventESSearchRequest['body']; apmEventClient: APMEventClient; }) { + const requiredFields = asMutableArray([SERVICE_NAME] as const); const res = await apmEventClient.search('get_service_name_from_traces', { apm: { sources: [ @@ -111,8 +116,13 @@ async function getServiceNameFromTraces({ }, ], }, - body: params, + body: { + ...params, + fields: requiredFields, + }, }); - return res.hits.hits[0]?._source.service.name; + const event = unflattenKnownApmEventFields(maybe(res.hits.hits[0])?.fields, requiredFields); + + return event?.service.name; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_metadata_for_dependency.ts b/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_metadata_for_dependency.ts index ebb3d3f57d18a..5b84743064142 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_metadata_for_dependency.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_metadata_for_dependency.ts @@ -7,8 +7,14 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { maybe } from '../../../common/utils/maybe'; -import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/es_fields/apm'; +import { + SPAN_DESTINATION_SERVICE_RESOURCE, + SPAN_SUBTYPE, + SPAN_TYPE, +} from '../../../common/es_fields/apm'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export interface MetadataForDependencyResponse { @@ -27,6 +33,7 @@ export async function getMetadataForDependency({ start: number; end: number; }): Promise { + const fields = asMutableArray([SPAN_TYPE, SPAN_SUBTYPE] as const); const sampleResponse = await apmEventClient.search('get_metadata_for_dependency', { apm: { events: [ProcessorEvent.span], @@ -46,16 +53,17 @@ export async function getMetadataForDependency({ ], }, }, + fields, sort: { '@timestamp': 'desc', }, }, }); - const sample = maybe(sampleResponse.hits.hits[0])?._source; + const sample = unflattenKnownApmEventFields(maybe(sampleResponse.hits.hits[0])?.fields); return { - spanType: sample?.span.type, - spanSubtype: sample?.span.subtype, + spanType: sample?.span?.type, + spanSubtype: sample?.span?.subtype, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts b/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts index 1c8448579806f..2a5a804d57f04 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts @@ -8,8 +8,11 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { kqlQuery, rangeQuery, termQuery, termsQuery } from '@kbn/observability-plugin/server'; import { keyBy } from 'lodash'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { AGENT_NAME, + AT_TIMESTAMP, EVENT_OUTCOME, SERVICE_ENVIRONMENT, SERVICE_NAME, @@ -66,6 +69,19 @@ export async function getTopDependencySpans({ sampleRangeFrom?: number; sampleRangeTo?: number; }): Promise { + const topDedsRequiredFields = asMutableArray([ + SPAN_ID, + TRACE_ID, + TRANSACTION_ID, + SPAN_NAME, + SERVICE_NAME, + SERVICE_ENVIRONMENT, + AGENT_NAME, + SPAN_DURATION, + EVENT_OUTCOME, + AT_TIMESTAMP, + ] as const); + const spans = ( await apmEventClient.search('get_top_dependency_spans', { apm: { @@ -98,23 +114,18 @@ export async function getTopDependencySpans({ ], }, }, - _source: [ - SPAN_ID, - TRACE_ID, - TRANSACTION_ID, - SPAN_NAME, - SERVICE_NAME, - SERVICE_ENVIRONMENT, - AGENT_NAME, - SPAN_DURATION, - EVENT_OUTCOME, - '@timestamp', - ], + fields: topDedsRequiredFields, }, }) - ).hits.hits.map((hit) => hit._source); + ).hits.hits.map((hit) => unflattenKnownApmEventFields(hit.fields, topDedsRequiredFields)); + + const transactionIds = spans.map((span) => span.transaction.id); - const transactionIds = spans.map((span) => span.transaction!.id); + const txRequiredFields = asMutableArray([ + TRANSACTION_ID, + TRANSACTION_TYPE, + TRANSACTION_NAME, + ] as const); const transactions = ( await apmEventClient.search('get_transactions_for_dependency_spans', { @@ -129,13 +140,13 @@ export async function getTopDependencySpans({ filter: [...termsQuery(TRANSACTION_ID, ...transactionIds)], }, }, - _source: [TRANSACTION_ID, TRANSACTION_TYPE, TRANSACTION_NAME], + fields: txRequiredFields, sort: { '@timestamp': 'desc', }, }, }) - ).hits.hits.map((hit) => hit._source); + ).hits.hits.map((hit) => unflattenKnownApmEventFields(hit.fields, txRequiredFields)); const transactionsById = keyBy(transactions, (transaction) => transaction.transaction.id); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts b/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts index 1e9576ea2f7e4..3d6fa0f5a5ef6 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts @@ -7,13 +7,17 @@ import { AggregationsAggregateOrder } from '@elastic/elasticsearch/lib/api/types'; import { kqlQuery, rangeQuery, termQuery, wildcardQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { + AT_TIMESTAMP, ERROR_CULPRIT, ERROR_EXC_HANDLED, ERROR_EXC_MESSAGE, ERROR_EXC_TYPE, ERROR_GROUP_ID, ERROR_GROUP_NAME, + ERROR_ID, ERROR_LOG_MESSAGE, SERVICE_NAME, TRACE_ID, @@ -93,6 +97,21 @@ export async function getErrorGroupMainStatistics({ ] : []; + const requiredFields = asMutableArray([ + TRACE_ID, + AT_TIMESTAMP, + ERROR_GROUP_ID, + ERROR_ID, + ] as const); + + const optionalFields = asMutableArray([ + ERROR_CULPRIT, + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ] as const); + const response = await apmEventClient.search('get_error_group_main_statistics', { apm: { sources: [ @@ -129,16 +148,8 @@ export async function getErrorGroupMainStatistics({ sample: { top_hits: { size: 1, - _source: [ - TRACE_ID, - ERROR_LOG_MESSAGE, - ERROR_EXC_MESSAGE, - ERROR_EXC_HANDLED, - ERROR_EXC_TYPE, - ERROR_CULPRIT, - ERROR_GROUP_ID, - '@timestamp', - ], + fields: [...requiredFields, ...optionalFields], + _source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, ERROR_EXC_HANDLED, ERROR_EXC_TYPE], sort: { '@timestamp': 'desc', }, @@ -157,15 +168,33 @@ export async function getErrorGroupMainStatistics({ const errorGroups = response.aggregations?.error_groups.buckets.map((bucket) => { + const errorSource = + 'error' in bucket.sample.hits.hits[0]._source + ? bucket.sample.hits.hits[0]._source + : undefined; + + const event = unflattenKnownApmEventFields(bucket.sample.hits.hits[0].fields, requiredFields); + + const mergedEvent = { + ...event, + error: { + ...(event.error ?? {}), + exception: + (errorSource?.error.exception?.length ?? 0) > 1 + ? errorSource?.error.exception + : event?.error.exception && [event.error.exception], + }, + }; + return { groupId: bucket.key as string, - name: getErrorName(bucket.sample.hits.hits[0]._source), - lastSeen: new Date(bucket.sample.hits.hits[0]._source['@timestamp']).getTime(), + name: getErrorName(mergedEvent), + lastSeen: new Date(mergedEvent[AT_TIMESTAMP]).getTime(), occurrences: bucket.doc_count, - culprit: bucket.sample.hits.hits[0]._source.error.culprit, - handled: bucket.sample.hits.hits[0]._source.error.exception?.[0].handled, - type: bucket.sample.hits.hits[0]._source.error.exception?.[0].type, - traceId: bucket.sample.hits.hits[0]._source.trace?.id, + culprit: mergedEvent.error.culprit, + handled: mergedEvent.error.exception?.[0].handled, + type: mergedEvent.error.exception?.[0].type, + traceId: mergedEvent.trace?.id, }; }) ?? []; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts b/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts index 0a154d3ad13fa..fc80c3f411651 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts @@ -6,6 +6,7 @@ */ import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { ERROR_GROUP_ID, @@ -42,6 +43,7 @@ export async function getErrorGroupSampleIds({ start: number; end: number; }): Promise { + const requiredFields = asMutableArray([ERROR_ID] as const); const resp = await apmEventClient.search('get_error_group_sample_ids', { apm: { sources: [ @@ -66,7 +68,7 @@ export async function getErrorGroupSampleIds({ should: [{ term: { [TRANSACTION_SAMPLED]: true } }], // prefer error samples with related transactions }, }, - _source: [ERROR_ID, 'transaction'], + fields: requiredFields, sort: asMutableArray([ { _score: { order: 'desc' } }, // sort by _score first to ensure that errors with transaction.sampled:true ends up on top { '@timestamp': { order: 'desc' } }, // sort by timestamp to get the most recent error @@ -74,8 +76,8 @@ export async function getErrorGroupSampleIds({ }, }); const errorSampleIds = resp.hits.hits.map((item) => { - const source = item._source; - return source.error.id; + const event = unflattenKnownApmEventFields(item.fields, requiredFields); + return event.error?.id; }); return { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_sample_details.ts b/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_sample_details.ts index 348949d3ecca5..91da19224d83c 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_sample_details.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_sample_details.ts @@ -6,7 +6,28 @@ */ import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; -import { ERROR_ID, SERVICE_NAME } from '../../../../common/es_fields/apm'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { maybe } from '../../../../common/utils/maybe'; +import { + AGENT_NAME, + AGENT_VERSION, + AT_TIMESTAMP, + ERROR_EXCEPTION, + ERROR_GROUP_ID, + ERROR_ID, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + PROCESSOR_EVENT, + PROCESSOR_NAME, + SERVICE_NAME, + TIMESTAMP_US, + TRACE_ID, + TRANSACTION_ID, + ERROR_STACK_TRACE, + SPAN_ID, +} from '../../../../common/es_fields/apm'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { ApmDocumentType } from '../../../../common/document_type'; import { RollupInterval } from '../../../../common/rollup'; @@ -17,7 +38,15 @@ import { APMError } from '../../../../typings/es_schemas/ui/apm_error'; export interface ErrorSampleDetailsResponse { transaction: Transaction | undefined; - error: APMError; + error: Omit & { + transaction?: { id?: string; type?: string }; + error: { + id: string; + } & Omit & { + exception?: APMError['error']['exception']; + log?: APMError['error']['log']; + }; + }; } export async function getErrorSampleDetails({ @@ -36,7 +65,29 @@ export async function getErrorSampleDetails({ apmEventClient: APMEventClient; start: number; end: number; -}): Promise { +}): Promise> { + const requiredFields = asMutableArray([ + AGENT_NAME, + PROCESSOR_EVENT, + TRACE_ID, + TIMESTAMP_US, + AT_TIMESTAMP, + SERVICE_NAME, + ERROR_ID, + ERROR_GROUP_ID, + ] as const); + + const optionalFields = asMutableArray([ + TRANSACTION_ID, + SPAN_ID, + AGENT_VERSION, + PROCESSOR_NAME, + ERROR_STACK_TRACE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ] as const); + const params = { apm: { sources: [ @@ -60,15 +111,29 @@ export async function getErrorSampleDetails({ ], }, }, + fields: [...requiredFields, ...optionalFields], + _source: [ERROR_EXCEPTION, 'error.log'], }, }; const resp = await apmEventClient.search('get_error_sample_details', params); - const error = resp.hits.hits[0]?._source; - const transactionId = error?.transaction?.id; - const traceId = error?.trace?.id; + const hit = maybe(resp.hits.hits[0]); + + if (!hit) { + return { + transaction: undefined, + error: undefined, + }; + } + + const source = 'error' in hit._source ? hit._source : undefined; - let transaction; + const errorFromFields = unflattenKnownApmEventFields(hit.fields, requiredFields); + + const transactionId = errorFromFields.transaction?.id ?? errorFromFields.span?.id; + const traceId = errorFromFields.trace.id; + + let transaction: Transaction | undefined; if (transactionId && traceId) { transaction = await getTransaction({ transactionId, @@ -81,6 +146,20 @@ export async function getErrorSampleDetails({ return { transaction, - error, + error: { + ...errorFromFields, + processor: { + name: errorFromFields.processor.name as 'error', + event: errorFromFields.processor.event as 'error', + }, + error: { + ...errorFromFields.error, + exception: + (source?.error.exception?.length ?? 0) > 1 + ? source?.error.exception + : errorFromFields?.error.exception && [errorFromFields.error.exception], + log: source?.error?.log, + }, + }, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/errors/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/errors/route.ts index dd262246a80b7..62d9d883ba896 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/errors/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/errors/route.ts @@ -7,6 +7,7 @@ import { jsonRt, toNumberRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; +import { notFound } from '@hapi/boom'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { ErrorDistributionResponse, getErrorDistribution } from './distribution/get_distribution'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; @@ -205,7 +206,7 @@ const errorGroupSampleDetailsRoute = createApmServerRoute({ const { serviceName, errorId } = params.path; const { environment, kuery, start, end } = params.query; - return getErrorSampleDetails({ + const { transaction, error } = await getErrorSampleDetails({ environment, errorId, kuery, @@ -214,6 +215,12 @@ const errorGroupSampleDetailsRoute = createApmServerRoute({ start, end, }); + + if (!error) { + throw notFound(); + } + + return { error, transaction }; }, }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts b/x-pack/plugins/observability_solution/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts index b0d3eabe0bab2..c606a6b045a93 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts @@ -8,6 +8,8 @@ import { AggregationsAggregateOrder } from '@elastic/elasticsearch/lib/api/types'; import { kqlQuery, rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../../../common/utils/as_mutable_array'; import { ERROR_CULPRIT, ERROR_TYPE, @@ -19,6 +21,7 @@ import { SERVICE_NAME, TRANSACTION_NAME, TRANSACTION_TYPE, + AT_TIMESTAMP, } from '../../../../../common/es_fields/apm'; import { environmentQuery } from '../../../../../common/utils/environment_query'; import { getErrorName } from '../../../../lib/helpers/get_error_name'; @@ -68,6 +71,16 @@ export async function getMobileCrashGroupMainStatistics({ ? { [maxTimestampAggKey]: sortDirection } : { _count: sortDirection }; + const requiredFields = asMutableArray([ERROR_GROUP_ID, AT_TIMESTAMP] as const); + + const optionalFields = asMutableArray([ + ERROR_CULPRIT, + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ] as const); + const response = await apmEventClient.search('get_crash_group_main_statistics', { apm: { events: [ProcessorEvent.error], @@ -99,22 +112,15 @@ export async function getMobileCrashGroupMainStatistics({ sample: { top_hits: { size: 1, - _source: [ - ERROR_LOG_MESSAGE, - ERROR_EXC_MESSAGE, - ERROR_EXC_HANDLED, - ERROR_EXC_TYPE, - ERROR_CULPRIT, - ERROR_GROUP_ID, - '@timestamp', - ], + fields: [...requiredFields, ...optionalFields], + _source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, ERROR_EXC_HANDLED, ERROR_EXC_TYPE], sort: { - '@timestamp': 'desc', + [AT_TIMESTAMP]: 'desc', }, }, }, ...(sortByLatestOccurrence - ? { [maxTimestampAggKey]: { max: { field: '@timestamp' } } } + ? { [maxTimestampAggKey]: { max: { field: AT_TIMESTAMP } } } : {}), }, }, @@ -123,14 +129,34 @@ export async function getMobileCrashGroupMainStatistics({ }); return ( - response.aggregations?.crash_groups.buckets.map((bucket) => ({ - groupId: bucket.key as string, - name: getErrorName(bucket.sample.hits.hits[0]._source), - lastSeen: new Date(bucket.sample.hits.hits[0]?._source['@timestamp']).getTime(), - occurrences: bucket.doc_count, - culprit: bucket.sample.hits.hits[0]?._source.error.culprit, - handled: bucket.sample.hits.hits[0]?._source.error.exception?.[0].handled, - type: bucket.sample.hits.hits[0]?._source.error.exception?.[0].type, - })) ?? [] + response.aggregations?.crash_groups.buckets.map((bucket) => { + const errorSource = + 'error' in bucket.sample.hits.hits[0]._source + ? bucket.sample.hits.hits[0]._source + : undefined; + + const event = unflattenKnownApmEventFields(bucket.sample.hits.hits[0].fields, requiredFields); + + const mergedEvent = { + ...event, + error: { + ...(event.error ?? {}), + exception: + (errorSource?.error.exception?.length ?? 0) > 1 + ? errorSource?.error.exception + : event?.error.exception && [event.error.exception], + }, + }; + + return { + groupId: event.error?.grouping_key, + name: getErrorName(mergedEvent), + lastSeen: new Date(mergedEvent[AT_TIMESTAMP]).getTime(), + occurrences: bucket.doc_count, + culprit: mergedEvent.error.culprit, + handled: mergedEvent.error.exception?.[0].handled, + type: mergedEvent.error.exception?.[0].type, + }; + }) ?? [] ); } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts b/x-pack/plugins/observability_solution/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts index f259e17d6154c..1181aa5b02870 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts @@ -8,7 +8,10 @@ import { AggregationsAggregateOrder } from '@elastic/elasticsearch/lib/api/types'; import { kqlQuery, rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { + AT_TIMESTAMP, ERROR_CULPRIT, ERROR_EXC_HANDLED, ERROR_EXC_MESSAGE, @@ -67,6 +70,16 @@ export async function getMobileErrorGroupMainStatistics({ ? { [maxTimestampAggKey]: sortDirection } : { _count: sortDirection }; + const requiredFields = asMutableArray([ERROR_GROUP_ID, AT_TIMESTAMP] as const); + + const optionalFields = asMutableArray([ + ERROR_CULPRIT, + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ] as const); + const response = await apmEventClient.search('get_error_group_main_statistics', { apm: { events: [ProcessorEvent.error], @@ -100,22 +113,15 @@ export async function getMobileErrorGroupMainStatistics({ sample: { top_hits: { size: 1, - _source: [ - ERROR_LOG_MESSAGE, - ERROR_EXC_MESSAGE, - ERROR_EXC_HANDLED, - ERROR_EXC_TYPE, - ERROR_CULPRIT, - ERROR_GROUP_ID, - '@timestamp', - ], + fields: [...requiredFields, ...optionalFields], + _source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, ERROR_EXC_HANDLED, ERROR_EXC_TYPE], sort: { - '@timestamp': 'desc', + [AT_TIMESTAMP]: 'desc', }, }, }, ...(sortByLatestOccurrence - ? { [maxTimestampAggKey]: { max: { field: '@timestamp' } } } + ? { [maxTimestampAggKey]: { max: { field: AT_TIMESTAMP } } } : {}), }, }, @@ -124,14 +130,34 @@ export async function getMobileErrorGroupMainStatistics({ }); return ( - response.aggregations?.error_groups.buckets.map((bucket) => ({ - groupId: bucket.key as string, - name: getErrorName(bucket.sample.hits.hits[0]._source), - lastSeen: new Date(bucket.sample.hits.hits[0]?._source['@timestamp']).getTime(), - occurrences: bucket.doc_count, - culprit: bucket.sample.hits.hits[0]?._source.error.culprit, - handled: bucket.sample.hits.hits[0]?._source.error.exception?.[0].handled, - type: bucket.sample.hits.hits[0]?._source.error.exception?.[0].type, - })) ?? [] + response.aggregations?.error_groups.buckets.map((bucket) => { + const errorSource = + 'error' in bucket.sample.hits.hits[0]._source + ? bucket.sample.hits.hits[0]._source + : undefined; + + const event = unflattenKnownApmEventFields(bucket.sample.hits.hits[0].fields, requiredFields); + + const mergedEvent = { + ...event, + error: { + ...(event.error ?? {}), + exception: + (errorSource?.error.exception?.length ?? 0) > 1 + ? errorSource?.error.exception + : event?.error.exception && [event.error.exception], + }, + }; + + return { + groupId: event.error?.grouping_key, + name: getErrorName(mergedEvent), + lastSeen: new Date(mergedEvent[AT_TIMESTAMP]).getTime(), + occurrences: bucket.doc_count, + culprit: mergedEvent.error.culprit, + handled: mergedEvent.error.exception?.[0].handled, + type: mergedEvent.error.exception?.[0].type, + }; + }) ?? [] ); } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/annotations/get_derived_service_annotations.ts index e766d56c44ae4..c683e308b73b8 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/annotations/get_derived_service_annotations.ts @@ -7,9 +7,12 @@ import type { ESFilter } from '@kbn/es-types'; import { rangeQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { maybe } from '../../../../common/utils/maybe'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; import { Annotation, AnnotationType } from '../../../../common/annotations'; -import { SERVICE_NAME, SERVICE_VERSION } from '../../../../common/es_fields/apm'; +import { AT_TIMESTAMP, SERVICE_NAME, SERVICE_VERSION } from '../../../../common/es_fields/apm'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBackwardCompatibleDocumentTypeFilter, @@ -66,6 +69,8 @@ export async function getDerivedServiceAnnotations({ if (versions.length <= 1) { return []; } + + const requiredFields = asMutableArray([AT_TIMESTAMP] as const); const annotations = await Promise.all( versions.map(async (version) => { const response = await apmEventClient.search('get_first_seen_of_version', { @@ -83,11 +88,21 @@ export async function getDerivedServiceAnnotations({ sort: { '@timestamp': 'asc', }, + fields: requiredFields, }, }); - const firstSeen = new Date(response.hits.hits[0]._source['@timestamp']).getTime(); + const event = unflattenKnownApmEventFields( + maybe(response.hits.hits[0])?.fields, + requiredFields + ); + + const timestamp = event?.[AT_TIMESTAMP]; + if (!timestamp) { + throw new Error('First seen for version was unexpectedly undefined or null.'); + } + const firstSeen = new Date(timestamp).getTime(); if (!isFiniteNumber(firstSeen)) { throw new Error('First seen for version was unexpectedly undefined or null.'); } @@ -99,7 +114,7 @@ export async function getDerivedServiceAnnotations({ return { type: AnnotationType.VERSION, id: version, - '@timestamp': firstSeen, + [AT_TIMESTAMP]: firstSeen, text: version, }; }) diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_agent.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_agent.ts index 94c5bcbac4e66..dd272eadf57d6 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_agent.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_agent.ts @@ -7,6 +7,8 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { AGENT_NAME, SERVICE_NAME, @@ -16,23 +18,7 @@ import { } from '../../../common/es_fields/apm'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getServerlessTypeFromCloudData, ServerlessType } from '../../../common/serverless'; - -interface ServiceAgent { - agent?: { - name: string; - }; - service?: { - runtime?: { - name?: string; - }; - }; - cloud?: { - provider?: string; - service?: { - name?: string; - }; - }; -} +import { maybe } from '../../../common/utils/maybe'; export interface ServiceAgentResponse { agentName?: string; @@ -51,6 +37,13 @@ export async function getServiceAgent({ start: number; end: number; }): Promise { + const fields = asMutableArray([ + AGENT_NAME, + SERVICE_RUNTIME_NAME, + CLOUD_PROVIDER, + CLOUD_SERVICE_NAME, + ] as const); + const params = { terminate_after: 1, apm: { @@ -90,6 +83,7 @@ export async function getServiceAgent({ ], }, }, + fields, sort: { _score: { order: 'desc' as const }, }, @@ -97,11 +91,14 @@ export async function getServiceAgent({ }; const response = await apmEventClient.search('get_service_agent_name', params); - if (response.hits.total.value === 0) { + const hit = maybe(response.hits.hits[0]); + if (!hit) { return {}; } - const { agent, service, cloud } = response.hits.hits[0]._source as ServiceAgent; + const event = unflattenKnownApmEventFields(hit.fields); + + const { agent, service, cloud } = event; const serverlessType = getServerlessTypeFromCloudData(cloud?.provider, cloud?.service?.name); return { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_container_metadata.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_container_metadata.ts index 400429617d803..d16910f5984fc 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_container_metadata.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_container_metadata.ts @@ -6,19 +6,20 @@ */ import { rangeQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { CONTAINER_ID, CONTAINER_IMAGE, KUBERNETES, KUBERNETES_POD_NAME, KUBERNETES_POD_UID, -} from '../../../common/es_fields/apm'; -import { KUBERNETES_CONTAINER_NAME, - KUBERNETES_NAMESPACE, KUBERNETES_REPLICASET_NAME, KUBERNETES_DEPLOYMENT_NAME, -} from '../../../common/es_fields/infra_metrics'; + KUBERNETES_CONTAINER_ID, + KUBERNETES_NAMESPACE, +} from '../../../common/es_fields/apm'; import { Kubernetes } from '../../../typings/es_schemas/raw/fields/kubernetes'; import { maybe } from '../../../common/utils/maybe'; import { InfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; @@ -51,9 +52,21 @@ export const getServiceInstanceContainerMetadata = async ({ { exists: { field: KUBERNETES_DEPLOYMENT_NAME } }, ]; + const fields = asMutableArray([ + KUBERNETES_POD_NAME, + KUBERNETES_POD_UID, + KUBERNETES_DEPLOYMENT_NAME, + KUBERNETES_CONTAINER_ID, + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_REPLICASET_NAME, + KUBERNETES_DEPLOYMENT_NAME, + ] as const); + const response = await infraMetricsClient.search({ size: 1, track_total_hits: false, + fields, query: { bool: { filter: [ @@ -69,7 +82,7 @@ export const getServiceInstanceContainerMetadata = async ({ }, }); - const sample = maybe(response.hits.hits[0])?._source as ServiceInstanceContainerMetadataDetails; + const sample = unflattenKnownApmEventFields(maybe(response.hits.hits[0])?.fields); return { kubernetes: { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_metadata_details.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_metadata_details.ts index daa49d2ed59c8..3c139f2aee0de 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_metadata_details.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_instance_metadata_details.ts @@ -7,7 +7,16 @@ import { merge } from 'lodash'; import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { METRICSET_NAME, SERVICE_NAME, SERVICE_NODE_NAME } from '../../../common/es_fields/apm'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { FlattenedApmEvent } from '@kbn/apm-data-access-plugin/server/utils/unflatten_known_fields'; +import { + AGENT_NAME, + AT_TIMESTAMP, + METRICSET_NAME, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + SERVICE_NODE_NAME, +} from '../../../common/es_fields/apm'; import { maybe } from '../../../common/utils/maybe'; import { getBackwardCompatibleDocumentTypeFilter, @@ -20,6 +29,13 @@ import { Container } from '../../../typings/es_schemas/raw/fields/container'; import { Kubernetes } from '../../../typings/es_schemas/raw/fields/kubernetes'; import { Host } from '../../../typings/es_schemas/raw/fields/host'; import { Cloud } from '../../../typings/es_schemas/raw/fields/cloud'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; +import { + SERVICE_METADATA_CLOUD_KEYS, + SERVICE_METADATA_CONTAINER_KEYS, + SERVICE_METADATA_INFRA_METRICS_KEYS, + SERVICE_METADATA_SERVICE_KEYS, +} from '../../../common/service_metadata'; export interface ServiceInstanceMetadataDetailsResponse { '@timestamp': string; @@ -50,6 +66,18 @@ export async function getServiceInstanceMetadataDetails({ ...rangeQuery(start, end), ]; + const requiredKeys = asMutableArray([AT_TIMESTAMP, SERVICE_NAME, AGENT_NAME] as const); + + const optionalKeys = asMutableArray([ + SERVICE_ENVIRONMENT, + ...SERVICE_METADATA_SERVICE_KEYS, + ...SERVICE_METADATA_CLOUD_KEYS, + ...SERVICE_METADATA_CONTAINER_KEYS, + ...SERVICE_METADATA_INFRA_METRICS_KEYS, + ] as const); + + const fields = [...requiredKeys, ...optionalKeys]; + async function getApplicationMetricSample() { const response = await apmEventClient.search( 'get_service_instance_metadata_details_application_metric', @@ -66,11 +94,12 @@ export async function getServiceInstanceMetadataDetails({ filter: filter.concat({ term: { [METRICSET_NAME]: 'app' } }), }, }, + fields, }, } ); - return maybe(response.hits.hits[0]?._source); + return unflattenKnownApmEventFields(maybe(response.hits.hits[0])?.fields, requiredKeys); } async function getTransactionEventSample() { @@ -85,11 +114,14 @@ export async function getServiceInstanceMetadataDetails({ terminate_after: 1, size: 1, query: { bool: { filter } }, + fields, }, } ); - return maybe(response.hits.hits[0]?._source); + return unflattenKnownApmEventFields( + maybe(response.hits.hits[0])?.fields as undefined | FlattenedApmEvent + ); } async function getTransactionMetricSample() { @@ -108,10 +140,14 @@ export async function getServiceInstanceMetadataDetails({ filter: filter.concat(getBackwardCompatibleDocumentTypeFilter(true)), }, }, + fields, }, } ); - return maybe(response.hits.hits[0]?._source); + + return unflattenKnownApmEventFields( + maybe(response.hits.hits[0])?.fields as undefined | FlattenedApmEvent + ); } // we can expect the most detail of application metrics, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_details.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_details.ts index fb44638d8a6b0..0319ae66039e5 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_details.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_details.ts @@ -7,37 +7,26 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { FlattenedApmEvent } from '@kbn/apm-data-access-plugin/server/utils/unflatten_known_fields'; import { environmentQuery } from '../../../common/utils/environment_query'; import { - AGENT, - CONTAINER, - CLOUD, CLOUD_AVAILABILITY_ZONE, CLOUD_REGION, CLOUD_MACHINE_TYPE, CLOUD_SERVICE_NAME, CONTAINER_ID, - HOST, - KUBERNETES, - SERVICE, SERVICE_NAME, SERVICE_NODE_NAME, SERVICE_VERSION, FAAS_ID, FAAS_TRIGGER_TYPE, - LABEL_TELEMETRY_AUTO_VERSION, } from '../../../common/es_fields/apm'; - import { ContainerType } from '../../../common/service_metadata'; -import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { should } from './get_service_metadata_icons'; import { isOpenTelemetryAgentName, hasOpenTelemetryPrefix } from '../../../common/agent_name'; - -type ServiceMetadataDetailsRaw = Pick< - TransactionRaw, - 'service' | 'agent' | 'host' | 'container' | 'kubernetes' | 'cloud' | 'labels' ->; +import { maybe } from '../../../common/utils/maybe'; export interface ServiceMetadataDetails { service?: { @@ -112,7 +101,6 @@ export async function getServiceMetadataDetails({ body: { track_total_hits: 1, size: 1, - _source: [SERVICE, AGENT, HOST, CONTAINER, KUBERNETES, CLOUD, LABEL_TELEMETRY_AUTO_VERSION], query: { bool: { filter, should } }, aggs: { serviceVersions: { @@ -166,13 +154,17 @@ export async function getServiceMetadataDetails({ }, totalNumberInstances: { cardinality: { field: SERVICE_NODE_NAME } }, }, + fields: ['*'], }, }; const response = await apmEventClient.search('get_service_metadata_details', params); - const hit = response.hits.hits[0]?._source as ServiceMetadataDetailsRaw | undefined; - if (!hit) { + const event = unflattenKnownApmEventFields( + maybe(response.hits.hits[0])?.fields as undefined | FlattenedApmEvent + ); + + if (!event) { return { service: undefined, container: undefined, @@ -180,7 +172,7 @@ export async function getServiceMetadataDetails({ }; } - const { service, agent, host, kubernetes, container, cloud, labels } = hit; + const { service, agent, host, kubernetes, container, cloud, labels } = event; const serviceMetadataDetails = { versions: response.aggregations?.serviceVersions.buckets.map((bucket) => bucket.key as string), diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_icons.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_icons.ts index 30580ddbf0ac8..ee0a857c9b719 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_icons.ts @@ -7,12 +7,15 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import type { FlattenedApmEvent } from '@kbn/apm-data-access-plugin/server/utils/unflatten_known_fields'; +import { maybe } from '../../../common/utils/maybe'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { AGENT_NAME, CLOUD_PROVIDER, CLOUD_SERVICE_NAME, CONTAINER_ID, - KUBERNETES, SERVICE_NAME, KUBERNETES_POD_NAME, HOST_OS_PLATFORM, @@ -20,14 +23,11 @@ import { AGENT_VERSION, SERVICE_FRAMEWORK_NAME, } from '../../../common/es_fields/apm'; -import { ContainerType } from '../../../common/service_metadata'; -import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; +import { ContainerType, SERVICE_METADATA_KUBERNETES_KEYS } from '../../../common/service_metadata'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { ServerlessType, getServerlessTypeFromCloudData } from '../../../common/serverless'; -type ServiceMetadataIconsRaw = Pick; - export interface ServiceMetadataIcons { agentName?: string; containerType?: ContainerType; @@ -61,6 +61,14 @@ export async function getServiceMetadataIcons({ }): Promise { const filter = [{ term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end)]; + const fields = asMutableArray([ + CLOUD_PROVIDER, + CONTAINER_ID, + AGENT_NAME, + CLOUD_SERVICE_NAME, + ...SERVICE_METADATA_KUBERNETES_KEYS, + ] as const); + const params = { apm: { events: [ @@ -72,8 +80,8 @@ export async function getServiceMetadataIcons({ body: { track_total_hits: 1, size: 1, - _source: [KUBERNETES, CLOUD_PROVIDER, CONTAINER_ID, AGENT_NAME, CLOUD_SERVICE_NAME], query: { bool: { filter, should } }, + fields, }, }; @@ -88,9 +96,11 @@ export async function getServiceMetadataIcons({ }; } - const { kubernetes, cloud, container, agent } = response.hits.hits[0] - ._source as ServiceMetadataIconsRaw; + const event = unflattenKnownApmEventFields( + maybe(response.hits.hits[0])?.fields as undefined | FlattenedApmEvent + ); + const { kubernetes, cloud, container, agent } = event ?? {}; let containerType: ContainerType; if (!!kubernetes) { containerType = 'Kubernetes'; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_children.ts b/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_children.ts index 3f9cf1275cace..2ff34698c20bc 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_children.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_children.ts @@ -7,6 +7,8 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { isEmpty } from 'lodash'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { PROCESSOR_EVENT, SPAN_ID, @@ -16,8 +18,6 @@ import { TRACE_ID, TRANSACTION_ID, } from '../../../common/es_fields/apm'; -import type { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; -import type { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getBufferedTimerange } from './utils'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; @@ -39,12 +39,16 @@ async function fetchLinkedChildrenOfSpan({ end, }); + const requiredFields = asMutableArray([TRACE_ID, PROCESSOR_EVENT] as const); + const optionalFields = asMutableArray([SPAN_ID, TRANSACTION_ID] as const); + const response = await apmEventClient.search('fetch_linked_children_of_span', { apm: { events: [ProcessorEvent.span, ProcessorEvent.transaction], }, - _source: [SPAN_LINKS, TRACE_ID, SPAN_ID, PROCESSOR_EVENT, TRANSACTION_ID], + _source: [SPAN_LINKS], body: { + fields: [...requiredFields, ...optionalFields], track_total_hits: false, size: 1000, query: { @@ -58,19 +62,32 @@ async function fetchLinkedChildrenOfSpan({ }, }, }); + + const linkedChildren = response.hits.hits.map((hit) => { + const source = 'span' in hit._source ? hit._source : undefined; + const event = unflattenKnownApmEventFields(hit.fields, requiredFields); + + return { + ...event, + span: { + ...event.span, + links: source?.span?.links ?? [], + }, + }; + }); // Filter out documents that don't have any span.links that match the combination of traceId and spanId - return response.hits.hits.filter(({ _source: source }) => { - const spanLinks = source.span?.links?.filter((spanLink) => { + return linkedChildren.filter((linkedChild) => { + const spanLinks = linkedChild?.span?.links?.filter((spanLink) => { return spanLink.trace.id === traceId && (spanId ? spanLink.span.id === spanId : true); }); return !isEmpty(spanLinks); }); } -function getSpanId(source: TransactionRaw | SpanRaw) { - return source.processor.event === ProcessorEvent.span - ? (source as SpanRaw).span.id - : (source as TransactionRaw).transaction?.id; +function getSpanId( + linkedChild: Awaited>[number] +): string { + return (linkedChild.span.id ?? linkedChild.transaction?.id) as string; } export async function getSpanLinksCountById({ @@ -90,8 +107,9 @@ export async function getSpanLinksCountById({ start, end, }); - return linkedChildren.reduce>((acc, { _source: source }) => { - source.span?.links?.forEach((link) => { + + return linkedChildren.reduce>((acc, item) => { + item.span?.links?.forEach((link) => { // Ignores span links that don't belong to this trace if (link.trace.id === traceId) { acc[link.span.id] = (acc[link.span.id] || 0) + 1; @@ -122,10 +140,10 @@ export async function getLinkedChildrenOfSpan({ end, }); - return linkedChildren.map(({ _source: source }) => { + return linkedChildren.map((item) => { return { - trace: { id: source.trace.id }, - span: { id: getSpanId(source) }, + trace: { id: item.trace.id }, + span: { id: getSpanId(item) }, }; }); } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_parents.ts b/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_parents.ts index 2010cd5e86f2f..59e91e0b17e6b 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_parents.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_linked_parents.ts @@ -56,7 +56,7 @@ export async function getLinkedParentsOfSpan({ }, }); - const source = response.hits.hits?.[0]?._source as TransactionRaw | SpanRaw; + const source = response.hits.hits?.[0]?._source as Pick; return source?.span?.links || []; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_span_links_details.ts b/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_span_links_details.ts index 13f47764af375..669adb1008080 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_span_links_details.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/span_links/get_span_links_details.ts @@ -7,6 +7,8 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { chunk, compact, isEmpty, keyBy } from 'lodash'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { SERVICE_NAME, SPAN_ID, @@ -25,8 +27,6 @@ import { import { Environment } from '../../../common/environment_rt'; import { SpanLinkDetails } from '../../../common/span_links'; import { SpanLink } from '../../../typings/es_schemas/raw/fields/span_links'; -import { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; -import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getBufferedTimerange } from './utils'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; @@ -48,26 +48,35 @@ async function fetchSpanLinksDetails({ end, }); + const requiredFields = asMutableArray([ + TRACE_ID, + SERVICE_NAME, + AGENT_NAME, + PROCESSOR_EVENT, + ] as const); + + const requiredTxFields = asMutableArray([ + TRANSACTION_ID, + TRANSACTION_NAME, + TRANSACTION_DURATION, + ] as const); + + const requiredSpanFields = asMutableArray([ + SPAN_ID, + SPAN_NAME, + SPAN_DURATION, + SPAN_SUBTYPE, + SPAN_TYPE, + ] as const); + + const optionalFields = asMutableArray([SERVICE_ENVIRONMENT] as const); + const response = await apmEventClient.search('get_span_links_details', { apm: { events: [ProcessorEvent.span, ProcessorEvent.transaction], }, - _source: [ - TRACE_ID, - SPAN_ID, - TRANSACTION_ID, - SERVICE_NAME, - SPAN_NAME, - TRANSACTION_NAME, - TRANSACTION_DURATION, - SPAN_DURATION, - PROCESSOR_EVENT, - SPAN_SUBTYPE, - SPAN_TYPE, - AGENT_NAME, - SERVICE_ENVIRONMENT, - ], body: { + fields: [...requiredFields, ...requiredTxFields, ...requiredSpanFields, ...optionalFields], track_total_hits: false, size: 1000, query: { @@ -106,16 +115,67 @@ async function fetchSpanLinksDetails({ const spanIdsMap = keyBy(spanLinks, 'span.id'); - return response.hits.hits.filter(({ _source: source }) => { - // The above query might return other spans from the same transaction because siblings spans share the same transaction.id - // so, if it is a span we need to guarantee that the span.id is the same as the span links ids - if (source.processor.event === ProcessorEvent.span) { - const span = source as SpanRaw; - const hasSpanId = spanIdsMap[span.span.id] || false; - return hasSpanId; - } - return true; - }); + return response.hits.hits + .filter((hit) => { + // The above query might return other spans from the same transaction because siblings spans share the same transaction.id + // so, if it is a span we need to guarantee that the span.id is the same as the span links ids + if (hit.fields[PROCESSOR_EVENT]?.[0] === ProcessorEvent.span) { + const spanLink = unflattenKnownApmEventFields(hit.fields, [ + ...requiredFields, + ...requiredSpanFields, + ]); + + const hasSpanId = Boolean(spanIdsMap[spanLink.span.id] || false); + return hasSpanId; + } + return true; + }) + .map((hit) => { + const commonEvent = unflattenKnownApmEventFields(hit.fields, requiredFields); + + const commonDetails = { + serviceName: commonEvent.service.name, + agentName: commonEvent.agent.name, + environment: commonEvent.service.environment as Environment, + transactionId: commonEvent.transaction?.id, + }; + + if (commonEvent.processor.event === ProcessorEvent.transaction) { + const event = unflattenKnownApmEventFields(hit.fields, [ + ...requiredFields, + ...requiredTxFields, + ]); + return { + traceId: event.trace.id, + spanId: event.transaction.id, + processorEvent: commonEvent.processor.event, + transactionId: event.transaction.id, + details: { + ...commonDetails, + spanName: event.transaction.name, + duration: event.transaction.duration.us, + }, + }; + } else { + const event = unflattenKnownApmEventFields(hit.fields, [ + ...requiredFields, + ...requiredSpanFields, + ]); + + return { + traceId: event.trace.id, + spanId: event.span.id, + processorEvent: commonEvent.processor.event, + details: { + ...commonDetails, + spanName: event.span.name, + duration: event.span.duration.us, + spanSubtype: event.span.subtype, + spanType: event.span.type, + }, + }; + } + }); } export async function getSpanLinksDetails({ @@ -153,39 +213,20 @@ export async function getSpanLinksDetails({ // Creates a map for all span links details found const spanLinksDetailsMap = linkedSpans.reduce>( - (acc, { _source: source }) => { - const commonDetails = { - serviceName: source.service.name, - agentName: source.agent.name, - environment: source.service.environment as Environment, - transactionId: source.transaction?.id, - }; - - if (source.processor.event === ProcessorEvent.transaction) { - const transaction = source as TransactionRaw; - const key = `${transaction.trace.id}:${transaction.transaction.id}`; + (acc, spanLink) => { + if (spanLink.processorEvent === ProcessorEvent.transaction) { + const key = `${spanLink.traceId}:${spanLink.transactionId}`; acc[key] = { - traceId: source.trace.id, - spanId: transaction.transaction.id, - details: { - ...commonDetails, - spanName: transaction.transaction.name, - duration: transaction.transaction.duration.us, - }, + traceId: spanLink.traceId, + spanId: spanLink.transactionId, + details: spanLink.details, }; } else { - const span = source as SpanRaw; - const key = `${span.trace.id}:${span.span.id}`; + const key = `${spanLink.traceId}:${spanLink.spanId}`; acc[key] = { - traceId: source.trace.id, - spanId: span.span.id, - details: { - ...commonDetails, - spanName: span.span.name, - duration: span.span.duration.us, - spanSubtype: span.span.subtype, - spanType: span.span.type, - }, + traceId: spanLink.traceId, + spanId: spanLink.spanId, + details: spanLink.details, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/traces/__snapshots__/queries.test.ts.snap b/x-pack/plugins/observability_solution/apm/server/routes/traces/__snapshots__/queries.test.ts.snap index d64c33a421e19..58ec97112e8f6 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/traces/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/observability_solution/apm/server/routes/traces/__snapshots__/queries.test.ts.snap @@ -12,15 +12,26 @@ Object { }, "body": Object { "_source": Array [ + "error.log.message", + "error.exception.message", + "error.exception.handled", + "error.exception.type", + ], + "fields": Array [ "timestamp.us", "trace.id", - "transaction.id", - "parent.id", "service.name", "error.id", - "error.log.message", - "error.exception", "error.grouping_key", + "processor.event", + "parent.id", + "transaction.id", + "span.id", + "error.culprit", + "error.log.message", + "error.exception.message", + "error.exception.handled", + "error.exception.type", ], "query": Object { "bool": Object { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_items.ts b/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_items.ts index d38a49745653a..55fb0aab47f38 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_items.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_items.ts @@ -10,12 +10,17 @@ import { SortResults } from '@elastic/elasticsearch/lib/api/types'; import { QueryDslQueryContainer, Sort } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { rangeQuery } from '@kbn/observability-plugin/server'; -import { last } from 'lodash'; +import { last, omit } from 'lodash'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { APMConfig } from '../..'; import { AGENT_NAME, CHILD_ID, - ERROR_EXCEPTION, + ERROR_CULPRIT, + ERROR_EXC_HANDLED, + ERROR_EXC_MESSAGE, + ERROR_EXC_TYPE, ERROR_GROUP_ID, ERROR_ID, ERROR_LOG_LEVEL, @@ -37,7 +42,7 @@ import { SPAN_SUBTYPE, SPAN_SYNC, SPAN_TYPE, - TIMESTAMP, + TIMESTAMP_US, TRACE_ID, TRANSACTION_DURATION, TRANSACTION_ID, @@ -84,6 +89,26 @@ export async function getTraceItems({ const maxTraceItems = maxTraceItemsFromUrlParam ?? config.ui.maxTraceItems; const excludedLogLevels = ['debug', 'info', 'warning']; + const requiredFields = asMutableArray([ + TIMESTAMP_US, + TRACE_ID, + SERVICE_NAME, + ERROR_ID, + ERROR_GROUP_ID, + PROCESSOR_EVENT, + ] as const); + + const optionalFields = asMutableArray([ + PARENT_ID, + TRANSACTION_ID, + SPAN_ID, + ERROR_CULPRIT, + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ] as const); + const errorResponsePromise = apmEventClient.search('get_errors_docs', { apm: { sources: [ @@ -96,23 +121,14 @@ export async function getTraceItems({ body: { track_total_hits: false, size: 1000, - _source: [ - TIMESTAMP, - TRACE_ID, - TRANSACTION_ID, - PARENT_ID, - SERVICE_NAME, - ERROR_ID, - ERROR_LOG_MESSAGE, - ERROR_EXCEPTION, - ERROR_GROUP_ID, - ], query: { bool: { filter: [{ term: { [TRACE_ID]: traceId } }, ...rangeQuery(start, end)], must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } }, }, }, + fields: [...requiredFields, ...optionalFields], + _source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, ERROR_EXC_HANDLED, ERROR_EXC_TYPE], }, }); @@ -133,8 +149,32 @@ export async function getTraceItems({ const traceDocsTotal = traceResponse.total; const exceedsMax = traceDocsTotal > maxTraceItems; - const traceDocs = traceResponse.hits.map((hit) => hit._source); - const errorDocs = errorResponse.hits.hits.map((hit) => hit._source); + + const traceDocs = traceResponse.hits.map(({ hit }) => hit); + + const errorDocs = errorResponse.hits.hits.map((hit) => { + const errorSource = 'error' in hit._source ? hit._source : undefined; + + const event = unflattenKnownApmEventFields(hit.fields, requiredFields); + + const waterfallErrorEvent: WaterfallError = { + ...event, + parent: { + ...event?.parent, + id: event?.parent?.id ?? event?.span?.id, + }, + error: { + ...(event.error ?? {}), + exception: + (errorSource?.error.exception?.length ?? 0) > 1 + ? errorSource?.error.exception + : event?.error.exception && [event.error.exception], + log: errorSource?.error.log, + }, + }; + + return waterfallErrorEvent; + }); return { exceedsMax, @@ -220,41 +260,54 @@ async function getTraceDocsPerPage({ start: number; end: number; searchAfter?: SortResults; -}) { +}): Promise<{ + hits: Array<{ hit: WaterfallTransaction | WaterfallSpan; sort: SortResults | undefined }>; + total: number; +}> { const size = Math.min(maxTraceItems, MAX_ITEMS_PER_PAGE); + const requiredFields = asMutableArray([ + AGENT_NAME, + TIMESTAMP_US, + TRACE_ID, + SERVICE_NAME, + PROCESSOR_EVENT, + ] as const); + + const requiredTxFields = asMutableArray([ + TRANSACTION_ID, + TRANSACTION_DURATION, + TRANSACTION_NAME, + TRANSACTION_TYPE, + ] as const); + + const requiredSpanFields = asMutableArray([ + SPAN_ID, + SPAN_TYPE, + SPAN_NAME, + SPAN_DURATION, + ] as const); + + const optionalFields = asMutableArray([ + PARENT_ID, + SERVICE_ENVIRONMENT, + EVENT_OUTCOME, + TRANSACTION_RESULT, + FAAS_COLDSTART, + SPAN_SUBTYPE, + SPAN_ACTION, + SPAN_COMPOSITE_COUNT, + SPAN_COMPOSITE_COMPRESSION_STRATEGY, + SPAN_COMPOSITE_SUM, + SPAN_SYNC, + CHILD_ID, + ] as const); + const body = { track_total_hits: true, size, search_after: searchAfter, - _source: [ - TIMESTAMP, - TRACE_ID, - PARENT_ID, - SERVICE_NAME, - SERVICE_ENVIRONMENT, - AGENT_NAME, - EVENT_OUTCOME, - PROCESSOR_EVENT, - TRANSACTION_DURATION, - TRANSACTION_ID, - TRANSACTION_NAME, - TRANSACTION_TYPE, - TRANSACTION_RESULT, - FAAS_COLDSTART, - SPAN_ID, - SPAN_TYPE, - SPAN_SUBTYPE, - SPAN_ACTION, - SPAN_NAME, - SPAN_DURATION, - SPAN_LINKS, - SPAN_COMPOSITE_COUNT, - SPAN_COMPOSITE_COMPRESSION_STRATEGY, - SPAN_COMPOSITE_SUM, - SPAN_SYNC, - CHILD_ID, - ], + _source: [SPAN_LINKS], query: { bool: { filter: [ @@ -266,6 +319,7 @@ async function getTraceDocsPerPage({ }, }, }, + fields: [...requiredFields, ...requiredTxFields, ...requiredSpanFields, ...optionalFields], sort: [ { _score: 'asc' }, { @@ -291,7 +345,51 @@ async function getTraceDocsPerPage({ }); return { - hits: res.hits.hits, + hits: res.hits.hits.map((hit) => { + const sort = hit.sort; + const spanLinksSource = 'span' in hit._source ? hit._source.span?.links : undefined; + + if (hit.fields[PROCESSOR_EVENT]?.[0] === ProcessorEvent.span) { + const spanEvent = unflattenKnownApmEventFields(hit.fields, [ + ...requiredFields, + ...requiredSpanFields, + ]); + + const spanWaterfallEvent: WaterfallSpan = { + ...omit(spanEvent, 'child'), + processor: { + event: 'span', + }, + span: { + ...spanEvent.span, + composite: spanEvent.span.composite + ? (spanEvent.span.composite as Required['composite']) + : undefined, + links: spanLinksSource, + }, + ...(spanEvent.child ? { child: spanEvent.child as WaterfallSpan['child'] } : {}), + }; + + return { sort, hit: spanWaterfallEvent }; + } + + const txEvent = unflattenKnownApmEventFields(hit.fields, [ + ...requiredFields, + ...requiredTxFields, + ]); + const txWaterfallEvent: WaterfallTransaction = { + ...txEvent, + processor: { + event: 'transaction', + }, + span: { + ...txEvent.span, + links: spanLinksSource, + }, + }; + + return { hit: txWaterfallEvent, sort }; + }), total: res.hits.total.value, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_samples_by_query.ts b/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_samples_by_query.ts index 033b666d0371c..dd1330dea4e48 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_samples_by_query.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_samples_by_query.ts @@ -89,10 +89,10 @@ export async function getTraceSamplesByQuery({ }, event_category_field: PROCESSOR_EVENT, query, - filter_path: 'hits.sequences.events._source.trace.id', + fields: [TRACE_ID], }) ).hits?.sequences?.flatMap((sequence) => - sequence.events.map((event) => (event._source as { trace: { id: string } }).trace.id) + sequence.events.map((event) => (event.fields as { [TRACE_ID]: [string] })[TRACE_ID][0]) ) ?? []; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts index 328201ec9d143..0814bcdc5738f 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts @@ -12,7 +12,10 @@ import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, probabilityRt, rangeRt } from '../default_api_types'; import { getTransaction } from '../transactions/get_transaction'; -import { getRootTransactionByTraceId } from '../transactions/get_transaction_by_trace'; +import { + type TransactionDetailRedirectInfo, + getRootTransactionByTraceId, +} from '../transactions/get_transaction_by_trace'; import { getTopTracesPrimaryStats, TopTracesPrimaryStatsResponse, @@ -128,7 +131,7 @@ const rootTransactionByTraceIdRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ - transaction: Transaction; + transaction?: TransactionDetailRedirectInfo; }> => { const { params: { @@ -155,7 +158,7 @@ const transactionByIdRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ - transaction: Transaction; + transaction?: Transaction; }> => { const { params: { @@ -191,7 +194,7 @@ const transactionByNameRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ - transaction: Transaction; + transaction?: TransactionDetailRedirectInfo; }> => { const { params: { @@ -295,7 +298,7 @@ const transactionFromTraceByIdRoute = createApmServerRoute({ query: rangeRt, }), options: { tags: ['access:apm'] }, - handler: async (resources): Promise => { + handler: async (resources): Promise => { const { params } = resources; const { path: { transactionId, traceId }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap b/x-pack/plugins/observability_solution/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap index deb1dec096f08..c7f832fe5ca65 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap @@ -11,6 +11,25 @@ Object { ], }, "body": Object { + "_source": Array [ + "span.links", + "transaction.agent.marks", + ], + "fields": Array [ + "trace.id", + "agent.name", + "processor.event", + "@timestamp", + "timestamp.us", + "service.name", + "transaction.id", + "transaction.duration.us", + "transaction.name", + "transaction.sampled", + "transaction.type", + "processor.name", + "service.language.name", + ], "query": Object { "bool": Object { "filter": Array [ @@ -311,6 +330,11 @@ Object { ], }, "body": Object { + "fields": Array [ + "transaction.id", + "trace.id", + "@timestamp", + ], "query": Object { "bool": Object { "filter": Array [ diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_span/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_span/index.ts index 4e1b7020a98a6..dc8ab0a6aab19 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_span/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_span/index.ts @@ -7,7 +7,11 @@ import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { SPAN_ID, TRACE_ID } from '../../../../common/es_fields/apm'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { FlattenedApmEvent } from '@kbn/apm-data-access-plugin/server/utils/unflatten_known_fields'; +import { merge, omit } from 'lodash'; +import { maybe } from '../../../../common/utils/maybe'; +import { SPAN_ID, SPAN_STACKTRACE, TRACE_ID } from '../../../../common/es_fields/apm'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getTransaction } from '../get_transaction'; @@ -38,6 +42,8 @@ export async function getSpan({ track_total_hits: false, size: 1, terminate_after: 1, + fields: ['*'], + _source: [SPAN_STACKTRACE], query: { bool: { filter: asMutableArray([ @@ -60,5 +66,17 @@ export async function getSpan({ : undefined, ]); - return { span: spanResp.hits.hits[0]?._source, parentTransaction }; + const hit = maybe(spanResp.hits.hits[0]); + const spanFromSource = hit && 'span' in hit._source ? hit._source : undefined; + + const event = unflattenKnownApmEventFields(hit?.fields as undefined | FlattenedApmEvent); + + return { + span: event + ? merge({}, omit(event, 'span.links'), spanFromSource, { + processor: { event: 'span' as const, name: 'transaction' as const }, + }) + : undefined, + parentTransaction, + }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction/index.ts index 8854f3075e59b..8fc9d93ceff87 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction/index.ts @@ -6,7 +6,26 @@ */ import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; -import { TRACE_ID, TRANSACTION_ID } from '../../../../common/es_fields/apm'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import type { Transaction } from '@kbn/apm-types'; +import { maybe } from '../../../../common/utils/maybe'; +import { + AGENT_NAME, + PROCESSOR_EVENT, + SERVICE_NAME, + TIMESTAMP_US, + TRACE_ID, + TRANSACTION_DURATION, + TRANSACTION_ID, + TRANSACTION_NAME, + TRANSACTION_SAMPLED, + TRANSACTION_TYPE, + AT_TIMESTAMP, + PROCESSOR_NAME, + SPAN_LINKS, + TRANSACTION_AGENT_MARKS, + SERVICE_LANGUAGE_NAME, +} from '../../../../common/es_fields/apm'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { ApmDocumentType } from '../../../../common/document_type'; @@ -24,7 +43,23 @@ export async function getTransaction({ apmEventClient: APMEventClient; start: number; end: number; -}) { +}): Promise { + const requiredFields = asMutableArray([ + TRACE_ID, + AGENT_NAME, + PROCESSOR_EVENT, + AT_TIMESTAMP, + TIMESTAMP_US, + SERVICE_NAME, + TRANSACTION_ID, + TRANSACTION_DURATION, + TRANSACTION_NAME, + TRANSACTION_SAMPLED, + TRANSACTION_TYPE, + ] as const); + + const optionalFields = asMutableArray([PROCESSOR_NAME, SERVICE_LANGUAGE_NAME] as const); + const resp = await apmEventClient.search('get_transaction', { apm: { sources: [ @@ -47,8 +82,37 @@ export async function getTransaction({ ]), }, }, + fields: [...requiredFields, ...optionalFields], + _source: [SPAN_LINKS, TRANSACTION_AGENT_MARKS], }, }); - return resp.hits.hits[0]?._source; + const hit = maybe(resp.hits.hits[0]); + + if (!hit) { + return undefined; + } + + const event = unflattenKnownApmEventFields(hit.fields, requiredFields); + + const source = + 'span' in hit._source && 'transaction' in hit._source + ? (hit._source as { + transaction: Pick['transaction'], 'marks'>; + span?: Pick['span'], 'links'>; + }) + : undefined; + + return { + ...event, + transaction: { + ...event.transaction, + marks: source?.transaction.marks, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + span: source?.span, + }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts index 75b4655d70117..160e3f736580a 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts @@ -6,11 +6,22 @@ */ import { rangeQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { maybe } from '../../../../common/utils/maybe'; import { ApmDocumentType } from '../../../../common/document_type'; -import { SERVICE_NAME, TRANSACTION_NAME } from '../../../../common/es_fields/apm'; +import { + AT_TIMESTAMP, + SERVICE_NAME, + TRACE_ID, + TRANSACTION_DURATION, + TRANSACTION_ID, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../common/es_fields/apm'; import { RollupInterval } from '../../../../common/rollup'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +import { TransactionDetailRedirectInfo } from '../get_transaction_by_trace'; export async function getTransactionByName({ transactionName, @@ -24,7 +35,17 @@ export async function getTransactionByName({ apmEventClient: APMEventClient; start: number; end: number; -}) { +}): Promise { + const requiredFields = asMutableArray([ + AT_TIMESTAMP, + TRACE_ID, + TRANSACTION_ID, + TRANSACTION_TYPE, + TRANSACTION_NAME, + TRANSACTION_DURATION, + SERVICE_NAME, + ] as const); + const resp = await apmEventClient.search('get_transaction', { apm: { sources: [ @@ -47,8 +68,9 @@ export async function getTransactionByName({ ]), }, }, + fields: requiredFields, }, }); - return resp.hits.hits[0]?._source; + return unflattenKnownApmEventFields(maybe(resp.hits.hits[0])?.fields, requiredFields); } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_trace/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_trace/index.ts index d27be0489f8da..803ae19a2228e 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_trace/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_trace/index.ts @@ -7,9 +7,40 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { rangeQuery } from '@kbn/observability-plugin/server'; -import { TRACE_ID, PARENT_ID } from '../../../../common/es_fields/apm'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { maybe } from '../../../../common/utils/maybe'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { + TRACE_ID, + PARENT_ID, + AT_TIMESTAMP, + TRANSACTION_DURATION, + TRANSACTION_ID, + TRANSACTION_NAME, + TRANSACTION_TYPE, + SERVICE_NAME, +} from '../../../../common/es_fields/apm'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +export interface TransactionDetailRedirectInfo { + [AT_TIMESTAMP]: string; + trace: { + id: string; + }; + transaction: { + id: string; + type: string; + name: string; + + duration: { + us: number; + }; + }; + service: { + name: string; + }; +} + export async function getRootTransactionByTraceId({ traceId, apmEventClient, @@ -20,7 +51,19 @@ export async function getRootTransactionByTraceId({ apmEventClient: APMEventClient; start: number; end: number; -}) { +}): Promise<{ + transaction: TransactionDetailRedirectInfo | undefined; +}> { + const requiredFields = asMutableArray([ + TRACE_ID, + TRANSACTION_ID, + TRANSACTION_NAME, + AT_TIMESTAMP, + TRANSACTION_TYPE, + TRANSACTION_DURATION, + SERVICE_NAME, + ] as const); + const params = { apm: { events: [ProcessorEvent.transaction as const], @@ -45,11 +88,15 @@ export async function getRootTransactionByTraceId({ filter: [{ term: { [TRACE_ID]: traceId } }, ...rangeQuery(start, end)], }, }, + fields: requiredFields, }, }; const resp = await apmEventClient.search('get_root_transaction_by_trace_id', params); + + const event = unflattenKnownApmEventFields(maybe(resp.hits.hits[0])?.fields, requiredFields); + return { - transaction: resp.hits.hits[0]?._source, + transaction: event, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/trace_samples/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/transactions/trace_samples/index.ts index 191250d3781ee..18dad19635333 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/transactions/trace_samples/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/trace_samples/index.ts @@ -7,7 +7,10 @@ import { Sort, QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; +import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { + AT_TIMESTAMP, SERVICE_NAME, TRACE_ID, TRANSACTION_ID, @@ -77,6 +80,8 @@ export async function getTraceSamples({ }); } + const requiredFields = asMutableArray([TRANSACTION_ID, TRACE_ID, AT_TIMESTAMP] as const); + const response = await apmEventClient.search('get_trace_samples_hits', { apm: { events: [ProcessorEvent.transaction], @@ -94,6 +99,7 @@ export async function getTraceSamples({ }, }, size: TRACE_SAMPLES_SIZE, + fields: requiredFields, sort: [ { _score: { @@ -101,7 +107,7 @@ export async function getTraceSamples({ }, }, { - '@timestamp': { + [AT_TIMESTAMP]: { order: 'desc', }, }, @@ -109,12 +115,15 @@ export async function getTraceSamples({ }, }); - const traceSamples = response.hits.hits.map((hit) => ({ - score: hit._score, - timestamp: hit._source['@timestamp'], - transactionId: hit._source.transaction.id, - traceId: hit._source.trace.id, - })); + const traceSamples = response.hits.hits.map((hit) => { + const event = unflattenKnownApmEventFields(hit.fields, requiredFields); + return { + score: hit._score, + timestamp: event[AT_TIMESTAMP], + transactionId: event.transaction.id, + traceId: event.trace.id, + }; + }); return { traceSamples }; }); diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/utils.ts b/x-pack/plugins/observability_solution/apm_data_access/server/utils.ts index 2fac072a8cdb5..71ceac4002919 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/utils.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/utils.ts @@ -15,3 +15,4 @@ export { } from './lib/helpers'; export { withApmSpan } from './utils/with_apm_span'; +export { unflattenKnownApmEventFields } from './utils/unflatten_known_fields'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.test.ts b/x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.test.ts new file mode 100644 index 0000000000000..9dc861a5292df --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.test.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { unflattenKnownApmEventFields } from './unflatten_known_fields'; + +describe('unflattenKnownApmEventFields', () => { + it('should return an empty object when input is empty', () => { + const input = {}; + const expectedOutput = {}; + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should correctly unflatten a simple flat input', () => { + const input = { + '@timestamp': '2024-10-10T10:10:10.000Z', + }; + const expectedOutput = { + '@timestamp': '2024-10-10T10:10:10.000Z', + }; + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should override unknown fields', () => { + const input = { + 'service.name': 'node-svc', + 'service.name.text': 'node-svc', + }; + const expectedOutput = { + service: { + name: 'node-svc', + }, + }; + + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should correctly unflatten multiple nested fields', () => { + const input = { + 'service.name': 'node-svc', + 'service.version': '1.0.0', + 'service.environment': 'production', + 'agent.name': 'nodejs', + }; + const expectedOutput = { + service: { + name: 'node-svc', + version: '1.0.0', + environment: 'production', + }, + agent: { + name: 'nodejs', + }, + }; + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should handle multiple values for multi-valued fields', () => { + const input = { + 'service.name': 'node-svc', + 'service.tags': ['foo', 'bar'], + }; + const expectedOutput = { + service: { + name: 'node-svc', + tags: ['foo', 'bar'], + }, + }; + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should correctly unflatten with empty multi-valued fields', () => { + const input = { + 'service.name': 'node-svc', + 'service.tags': [], + }; + const expectedOutput = { + service: { + name: 'node-svc', + tags: [], + }, + }; + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should retain unknown fields in the output', () => { + const input = { + 'service.name': 'node-svc', + 'unknown.texts': ['foo', 'bar'], + 'unknown.field': 'foo', + unknonwField: 'bar', + }; + const expectedOutput = { + service: { + name: 'node-svc', + }, + unknown: { + field: 'foo', + texts: ['foo', 'bar'], + }, + unknonwField: 'bar', + }; + expect(unflattenKnownApmEventFields(input)).toEqual(expectedOutput); + }); + + it('should correctly unflatten nested fields with mandatory field', () => { + const input = { + 'service.name': 'node-svc', + 'service.environment': undefined, + }; + + const requiredFields: ['service.name'] = ['service.name']; + + const expectedOutput = { + service: { + name: 'node-svc', + }, + }; + expect(unflattenKnownApmEventFields(input, requiredFields)).toEqual(expectedOutput); + }); + + it('should throw an exception when mandatory field is not in the input', () => { + const input = { + 'service.environment': 'PROD', + }; + + const requiredFields: ['service.name'] = ['service.name']; + + // @ts-expect-error + expect(() => unflattenKnownApmEventFields(input, requiredFields)).toThrowError( + 'Missing required fields service.name in event' + ); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.ts b/x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.ts new file mode 100644 index 0000000000000..b9a4322269828 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/utils/unflatten_known_fields.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DedotObject } from '@kbn/utility-types'; +import * as APM_EVENT_FIELDS_MAP from '@kbn/apm-types/es_fields'; +import type { ValuesType } from 'utility-types'; +import { unflattenObject } from '@kbn/observability-utils/object/unflatten_object'; +import { mergePlainObjects } from '@kbn/observability-utils/object/merge_plain_objects'; +import { castArray, isArray } from 'lodash'; +import { AgentName } from '@kbn/elastic-agent-utils'; +import { EventOutcome } from '@kbn/apm-types/src/es_schemas/raw/fields'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; + +const { + CLOUD, + AGENT, + SERVICE, + ERROR_EXCEPTION, + SPAN_LINKS, + HOST, + KUBERNETES, + CONTAINER, + TIER, + INDEX, + DATA_STEAM_TYPE, + VALUE_OTEL_JVM_PROCESS_MEMORY_HEAP, + VALUE_OTEL_JVM_PROCESS_MEMORY_NON_HEAP, + SPAN_LINKS_SPAN_ID, + SPAN_LINKS_TRACE_ID, + SPAN_STACKTRACE, + ...CONCRETE_FIELDS +} = APM_EVENT_FIELDS_MAP; + +const ALL_FIELDS = Object.values(CONCRETE_FIELDS); + +const KNOWN_MULTI_VALUED_FIELDS = [ + APM_EVENT_FIELDS_MAP.CHILD_ID, + APM_EVENT_FIELDS_MAP.PROCESS_ARGS, +] as const; + +type KnownField = ValuesType; + +type KnownSingleValuedField = Exclude; +type KnownMultiValuedField = ValuesType; + +const KNOWN_SINGLE_VALUED_FIELDS = ALL_FIELDS.filter( + (field): field is KnownSingleValuedField => !KNOWN_MULTI_VALUED_FIELDS.includes(field as any) +); + +interface TypeOverrideMap { + [APM_EVENT_FIELDS_MAP.SPAN_DURATION]: number; + [APM_EVENT_FIELDS_MAP.AGENT_NAME]: AgentName; + [APM_EVENT_FIELDS_MAP.EVENT_OUTCOME]: EventOutcome; + [APM_EVENT_FIELDS_MAP.FAAS_COLDSTART]: true; + [APM_EVENT_FIELDS_MAP.TRANSACTION_DURATION]: number; + [APM_EVENT_FIELDS_MAP.TIMESTAMP_US]: number; + [APM_EVENT_FIELDS_MAP.PROCESSOR_EVENT]: ProcessorEvent; + [APM_EVENT_FIELDS_MAP.SPAN_COMPOSITE_COUNT]: number; + [APM_EVENT_FIELDS_MAP.SPAN_COMPOSITE_SUM]: number; + [APM_EVENT_FIELDS_MAP.SPAN_SYNC]: boolean; + [APM_EVENT_FIELDS_MAP.TRANSACTION_SAMPLED]: boolean; + [APM_EVENT_FIELDS_MAP.PROCESSOR_NAME]: 'transaction' | 'metric' | 'error'; + [APM_EVENT_FIELDS_MAP.HTTP_RESPONSE_STATUS_CODE]: number; + [APM_EVENT_FIELDS_MAP.PROCESS_PID]: number; + [APM_EVENT_FIELDS_MAP.OBSERVER_VERSION_MAJOR]: number; + [APM_EVENT_FIELDS_MAP.ERROR_EXC_HANDLED]: boolean; +} + +type MaybeMultiValue = T extends KnownMultiValuedField ? U[] : U; + +type TypeOfKnownField = MaybeMultiValue< + T, + T extends keyof TypeOverrideMap ? TypeOverrideMap[T] : string +>; + +type MapToSingleOrMultiValue> = { + [TKey in keyof T]: TKey extends KnownField + ? T[TKey] extends undefined + ? TypeOfKnownField | undefined + : TypeOfKnownField + : unknown; +}; + +type UnflattenedKnownFields> = DedotObject< + MapToSingleOrMultiValue +>; + +export type FlattenedApmEvent = Record; + +export type UnflattenedApmEvent = UnflattenedKnownFields; + +export function unflattenKnownApmEventFields | undefined = undefined>( + fields: T +): T extends Record ? UnflattenedKnownFields : undefined; + +export function unflattenKnownApmEventFields< + T extends Record | undefined, + U extends Array> +>( + fields: T, + required: U +): T extends Record + ? UnflattenedKnownFields & + (U extends any[] + ? UnflattenedKnownFields<{ + [TKey in ValuesType]: keyof T extends TKey ? T[TKey] : unknown[]; + }> + : {}) + : undefined; + +export function unflattenKnownApmEventFields( + hitFields?: Record, + requiredFields?: string[] +) { + if (!hitFields) { + return undefined; + } + const missingRequiredFields = + requiredFields?.filter((key) => { + const value = hitFields?.[key]; + return value === null || value === undefined || (isArray(value) && value.length === 0); + }) ?? []; + + if (missingRequiredFields.length > 0) { + throw new Error(`Missing required fields ${missingRequiredFields.join(', ')} in event`); + } + + const copy: Record = mapToSingleOrMultiValue({ + ...hitFields, + }); + + const [knownFields, unknownFields] = Object.entries(copy).reduce( + (prev, [key, value]) => { + if (ALL_FIELDS.includes(key as KnownField)) { + prev[0][key as KnownField] = value; + } else { + prev[1][key] = value; + } + return prev; + }, + [{} as Record, {} as Record] + ); + + const unflattened = mergePlainObjects( + {}, + unflattenObject(unknownFields), + unflattenObject(knownFields) + ); + + return unflattened; +} + +export function mapToSingleOrMultiValue>( + fields: T +): MapToSingleOrMultiValue { + KNOWN_SINGLE_VALUED_FIELDS.forEach((field) => { + const value = fields[field]; + if (value !== null && value !== undefined) { + fields[field as keyof T] = castArray(value)[0]; + } + }); + + return fields; +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json b/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json index ea3ebf77b25be..aeeb73bee2857 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json @@ -20,6 +20,8 @@ "@kbn/apm-utils", "@kbn/core-http-server", "@kbn/security-plugin-types-server", - "@kbn/observability-utils" + "@kbn/observability-utils", + "@kbn/utility-types", + "@kbn/elastic-agent-utils" ] } From b93d3c224aeae33fa59482094c9927f0358c6ec8 Mon Sep 17 00:00:00 2001 From: mohamedhamed-ahmed Date: Tue, 15 Oct 2024 10:40:09 +0100 Subject: [PATCH 08/84] [Dataset Quality] Introduce Kibana Management Feature (#194825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes [#3874](https://github.com/elastic/observability-dev/issues/3874) ## 📝 Summary This PR adds new kibana privilege feature to control access to `Data Set Quality` page under Stack Management's `Data` section. Had to fix a lot of tests since the `kibana_admin` role gets access by default to all kibana features one of which now is the `Data Set Quality` page. At the same time this made the `Data` section visible to any user with `kibana_admin` role. ## 🎥 Demo https://github.com/user-attachments/assets/ce8c8110-f6f4-44b8-a4e7-5f2dd3deda66 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../project_roles/security/roles.yml | 1 + x-pack/plugins/data_quality/common/index.ts | 1 + x-pack/plugins/data_quality/public/plugin.ts | 86 ++++++++++--------- .../plugins/data_quality/server/features.ts | 77 +++++++++++++++++ x-pack/plugins/data_quality/server/plugin.ts | 42 +-------- x-pack/plugins/data_quality/tsconfig.json | 1 + .../apis/features/features/features.ts | 2 + .../apis/security/privileges.ts | 1 + .../apis/security/privileges_basic.ts | 2 + .../feature_controls/api_keys_security.ts | 9 +- .../feature_controls/ccr_security.ts | 11 ++- .../dataset_quality_privileges.ts | 6 +- .../feature_controls/ilm_security.ts | 11 ++- .../index_management_security.ts | 13 ++- .../ingest_pipelines_security.ts | 9 +- .../license_management_security.ts | 9 +- .../feature_controls/logstash_security.ts | 9 +- .../feature_controls/management_security.ts | 7 +- .../remote_clusters_security.ts | 9 +- .../feature_controls/transform_security.ts | 9 +- .../upgrade_assistant_security.ts | 9 +- .../spaces_only/telemetry/telemetry.ts | 1 + .../security_and_spaces/tests/nav_links.ts | 3 +- 23 files changed, 221 insertions(+), 107 deletions(-) create mode 100644 x-pack/plugins/data_quality/server/features.ts diff --git a/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml b/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml index 3c008407d5c46..e9223cd5d73ef 100644 --- a/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml +++ b/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml @@ -55,6 +55,7 @@ viewer: - feature_dashboard.all - feature_maps.all - feature_visualize.all + - feature_dataQuality.all resources: '*' run_as: [] diff --git a/x-pack/plugins/data_quality/common/index.ts b/x-pack/plugins/data_quality/common/index.ts index a1869cd9ac356..f6de79310eff5 100644 --- a/x-pack/plugins/data_quality/common/index.ts +++ b/x-pack/plugins/data_quality/common/index.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; export const PLUGIN_ID = 'data_quality'; +export const PLUGIN_FEATURE_ID = 'dataQuality'; export const PLUGIN_NAME = i18n.translate('xpack.dataQuality.name', { defaultMessage: 'Data Set Quality', }); diff --git a/x-pack/plugins/data_quality/public/plugin.ts b/x-pack/plugins/data_quality/public/plugin.ts index 025268848a9a8..27639f896ab60 100644 --- a/x-pack/plugins/data_quality/public/plugin.ts +++ b/x-pack/plugins/data_quality/public/plugin.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { Capabilities, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import { MANAGEMENT_APP_LOCATOR } from '@kbn/deeplinks-management/constants'; import { ManagementAppLocatorParams } from '@kbn/management-plugin/common/locator'; +import { Subject } from 'rxjs'; import { DataQualityPluginSetup, DataQualityPluginStart, @@ -30,6 +31,8 @@ export class DataQualityPlugin AppPluginStartDependencies > { + private capabilities$ = new Subject(); + public setup( core: CoreSetup, plugins: AppPluginSetupDependencies @@ -37,51 +40,56 @@ export class DataQualityPlugin const { management, share } = plugins; const useHash = core.uiSettings.get('state:storeInSessionStorage'); - management.sections.section.data.registerApp({ - id: PLUGIN_ID, - title: PLUGIN_NAME, - order: 2, - keywords: [ - 'data', - 'quality', - 'data quality', - 'datasets', - 'datasets quality', - 'data set quality', - ], - async mount(params: ManagementAppMountParams) { - const [{ renderApp }, [coreStart, pluginsStartDeps, pluginStart]] = await Promise.all([ - import('./application'), - core.getStartServices(), - ]); + this.capabilities$.subscribe((capabilities) => { + if (!capabilities.dataQuality.show) return; - return renderApp(coreStart, pluginsStartDeps, pluginStart, params); - }, - hideFromSidebar: false, - }); + management.sections.section.data.registerApp({ + id: PLUGIN_ID, + title: PLUGIN_NAME, + order: 2, + keywords: [ + 'data', + 'quality', + 'data quality', + 'datasets', + 'datasets quality', + 'data set quality', + ], + async mount(params: ManagementAppMountParams) { + const [{ renderApp }, [coreStart, pluginsStartDeps, pluginStart]] = await Promise.all([ + import('./application'), + core.getStartServices(), + ]); - const managementLocator = - share.url.locators.get(MANAGEMENT_APP_LOCATOR); + return renderApp(coreStart, pluginsStartDeps, pluginStart, params); + }, + hideFromSidebar: false, + }); - if (managementLocator) { - share.url.locators.create( - new DatasetQualityLocatorDefinition({ - useHash, - managementLocator, - }) - ); - share.url.locators.create( - new DatasetQualityDetailsLocatorDefinition({ - useHash, - managementLocator, - }) - ); - } + const managementLocator = + share.url.locators.get(MANAGEMENT_APP_LOCATOR); + + if (managementLocator) { + share.url.locators.create( + new DatasetQualityLocatorDefinition({ + useHash, + managementLocator, + }) + ); + share.url.locators.create( + new DatasetQualityDetailsLocatorDefinition({ + useHash, + managementLocator, + }) + ); + } + }); return {}; } - public start(_core: CoreStart): DataQualityPluginStart { + public start(core: CoreStart): DataQualityPluginStart { + this.capabilities$.next(core.application.capabilities); return {}; } diff --git a/x-pack/plugins/data_quality/server/features.ts b/x-pack/plugins/data_quality/server/features.ts new file mode 100644 index 0000000000000..a570c78e6edbe --- /dev/null +++ b/x-pack/plugins/data_quality/server/features.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { + KibanaFeatureConfig, + KibanaFeatureScope, + ElasticsearchFeatureConfig, +} from '@kbn/features-plugin/common'; +import { PLUGIN_FEATURE_ID, PLUGIN_ID, PLUGIN_NAME } from '../common'; + +export const KIBANA_FEATURE: KibanaFeatureConfig = { + id: PLUGIN_FEATURE_ID, + name: PLUGIN_NAME, + category: DEFAULT_APP_CATEGORIES.management, + scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security], + app: [PLUGIN_ID], + privileges: { + all: { + app: [PLUGIN_ID], + savedObject: { + all: [], + read: [], + }, + ui: ['show'], + }, + read: { + disabled: true, + savedObject: { + all: [], + read: [], + }, + ui: ['show'], + }, + }, +}; + +export const ELASTICSEARCH_FEATURE: ElasticsearchFeatureConfig = { + id: PLUGIN_ID, + management: { + data: [PLUGIN_ID], + }, + privileges: [ + { + ui: [], + requiredClusterPrivileges: [], + requiredIndexPrivileges: { + ['logs-*-*']: ['read'], + }, + }, + { + ui: [], + requiredClusterPrivileges: [], + requiredIndexPrivileges: { + ['traces-*-*']: ['read'], + }, + }, + { + ui: [], + requiredClusterPrivileges: [], + requiredIndexPrivileges: { + ['metrics-*-*']: ['read'], + }, + }, + { + ui: [], + requiredClusterPrivileges: [], + requiredIndexPrivileges: { + ['synthetics-*-*']: ['read'], + }, + }, + ], +}; diff --git a/x-pack/plugins/data_quality/server/plugin.ts b/x-pack/plugins/data_quality/server/plugin.ts index 1b7e9cface597..93ed93917fa7a 100644 --- a/x-pack/plugins/data_quality/server/plugin.ts +++ b/x-pack/plugins/data_quality/server/plugin.ts @@ -6,48 +6,14 @@ */ import { CoreSetup, Plugin } from '@kbn/core/server'; -import { PLUGIN_ID } from '../common'; import { Dependencies } from './types'; +import { ELASTICSEARCH_FEATURE, KIBANA_FEATURE } from './features'; export class DataQualityPlugin implements Plugin { - public setup(coreSetup: CoreSetup, { features }: Dependencies) { - features.registerElasticsearchFeature({ - id: PLUGIN_ID, - management: { - data: [PLUGIN_ID], - }, - privileges: [ - { - ui: [], - requiredClusterPrivileges: [], - requiredIndexPrivileges: { - ['logs-*-*']: ['read'], - }, - }, - { - ui: [], - requiredClusterPrivileges: [], - requiredIndexPrivileges: { - ['traces-*-*']: ['read'], - }, - }, - { - ui: [], - requiredClusterPrivileges: [], - requiredIndexPrivileges: { - ['metrics-*-*']: ['read'], - }, - }, - { - ui: [], - requiredClusterPrivileges: [], - requiredIndexPrivileges: { - ['synthetics-*-*']: ['read'], - }, - }, - ], - }); + public setup(_coreSetup: CoreSetup, { features }: Dependencies) { + features.registerKibanaFeature(KIBANA_FEATURE); + features.registerElasticsearchFeature(ELASTICSEARCH_FEATURE); } public start() {} diff --git a/x-pack/plugins/data_quality/tsconfig.json b/x-pack/plugins/data_quality/tsconfig.json index 911c4fbfff557..a3f04f88ec7ff 100644 --- a/x-pack/plugins/data_quality/tsconfig.json +++ b/x-pack/plugins/data_quality/tsconfig.json @@ -28,6 +28,7 @@ "@kbn/deeplinks-management", "@kbn/deeplinks-observability", "@kbn/ebt-tools", + "@kbn/core-application-common", ], "exclude": ["target/**/*"] } diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index 895bfcb851bdd..547fd12a54203 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -98,6 +98,7 @@ export default function ({ getService }: FtrProviderContext) { 'discover', 'visualize', 'dashboard', + 'dataQuality', 'dev_tools', 'actions', 'enterpriseSearch', @@ -147,6 +148,7 @@ export default function ({ getService }: FtrProviderContext) { 'discover', 'visualize', 'dashboard', + 'dataQuality', 'dev_tools', 'actions', 'enterpriseSearch', diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 51ce417cfe695..23838eda12efb 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -90,6 +90,7 @@ export default function ({ getService }: FtrProviderContext) { ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], + dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], apm: ['all', 'read', 'minimal_all', 'minimal_read'], discover: [ 'all', diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index dda148359ac16..d204c3ed8345f 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -59,6 +59,7 @@ export default function ({ getService }: FtrProviderContext) { guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'], aiAssistantManagementSelection: ['all', 'read', 'minimal_all', 'minimal_read'], inventory: ['all', 'read', 'minimal_all', 'minimal_read'], + dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], }, global: ['all', 'read'], space: ['all', 'read'], @@ -177,6 +178,7 @@ export default function ({ getService }: FtrProviderContext) { ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], + dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], apm: ['all', 'read', 'minimal_all', 'minimal_read'], discover: [ 'all', diff --git a/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts b/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts index 938be328e4b1c..9e4b27b078885 100644 --- a/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts +++ b/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts @@ -35,8 +35,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not render the "Security" section', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); diff --git a/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts b/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts index 607b27abbb8df..80fd4a2ba8374 100644 --- a/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts +++ b/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts @@ -40,10 +40,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('"Data" section', function () { this.tags('skipFIPS'); - it('should not render', async () => { + it('should render only data_quality section', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts index 949d42dbd31c4..e196f92c1cf18 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid // Index logs for synth-* and apache.access datasets await synthtrace.index(getInitialTestLogs({ to, count: 4 })); - await createDatasetQualityUserWithRole(security, 'dataset_quality_no_read', []); + await createDatasetQualityUserWithRole(security, 'dataset_quality_no_read', [], false); // Logout in order to re-login with a different user await PageObjects.security.forceLogout(); @@ -197,7 +197,8 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid async function createDatasetQualityUserWithRole( security: ReturnType, username: string, - indices: Array<{ names: string[]; privileges: string[] }> + indices: Array<{ names: string[]; privileges: string[] }>, + hasDataQualityPrivileges = true ) { const role = `${username}-role`; const password = `${username}-password`; @@ -211,6 +212,7 @@ async function createDatasetQualityUserWithRole( kibana: [ { feature: { + dataQuality: [hasDataQualityPrivileges ? 'all' : 'none'], discover: ['all'], fleet: ['none'], }, diff --git a/x-pack/test/functional/apps/index_lifecycle_management/feature_controls/ilm_security.ts b/x-pack/test/functional/apps/index_lifecycle_management/feature_controls/ilm_security.ts index d51161381d68c..f3f7cbeefbbd1 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/feature_controls/ilm_security.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/feature_controls/ilm_security.ts @@ -40,10 +40,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('"Data" section', function () { this.tags('skipFIPS'); - it('should not render', async () => { + it('should render only data_quality section', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/index_management/feature_controls/index_management_security.ts b/x-pack/test/functional/apps/index_management/feature_controls/index_management_security.ts index c8d07cfa98a3d..9d267f2ed7c33 100644 --- a/x-pack/test/functional/apps/index_management/feature_controls/index_management_security.ts +++ b/x-pack/test/functional/apps/index_management/feature_controls/index_management_security.ts @@ -40,10 +40,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('"Data" section', function () { this.tags('skipFIPS'); - it('should not render', async () => { + it('should render only data_quality section', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); @@ -71,7 +76,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(sections).to.have.length(2); expect(sections[0]).to.eql({ sectionId: 'data', - sectionLinks: ['index_management', 'data_quality', 'transform'], + sectionLinks: ['index_management', 'transform'], }); }); }); diff --git a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts index acaecc481acb2..1e6d1c0757383 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts @@ -43,8 +43,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { this.tags('skipFIPS'); it('should not render', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/license_management/feature_controls/license_management_security.ts b/x-pack/test/functional/apps/license_management/feature_controls/license_management_security.ts index 60671662ba1a7..ee0a0a5f8988a 100644 --- a/x-pack/test/functional/apps/license_management/feature_controls/license_management_security.ts +++ b/x-pack/test/functional/apps/license_management/feature_controls/license_management_security.ts @@ -42,8 +42,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { this.tags('skipFIPS'); it('should not render', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/logstash/feature_controls/logstash_security.ts b/x-pack/test/functional/apps/logstash/feature_controls/logstash_security.ts index 0819a3b2f1a0c..0167b21610314 100644 --- a/x-pack/test/functional/apps/logstash/feature_controls/logstash_security.ts +++ b/x-pack/test/functional/apps/logstash/feature_controls/logstash_security.ts @@ -42,8 +42,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { this.tags('skipFIPS'); it('should not render', async function () { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts index a3b838b5aa361..4e0b41270d231 100644 --- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts +++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts @@ -63,8 +63,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should only render management entries controllable via Kibana privileges', async () => { await PageObjects.common.navigateToApp('management'); const sections = await managementMenu.getSections(); - expect(sections).to.have.length(2); - expect(sections[0]).to.eql({ + expect(sections).to.have.length(3); + expect(sections[0]).to.eql({ sectionId: 'data', sectionLinks: ['data_quality'] }); + expect(sections[1]).to.eql({ sectionId: 'insightsAndAlerting', sectionLinks: [ 'triggersActionsAlerts', @@ -75,7 +76,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'maintenanceWindows', ], }); - expect(sections[1]).to.eql({ + expect(sections[2]).to.eql({ sectionId: 'kibana', sectionLinks: [ 'dataViews', diff --git a/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts index af9330f303349..1bcde15660a15 100644 --- a/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts +++ b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts @@ -42,8 +42,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { this.tags('skipFIPS'); it('should not render', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts b/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts index a17d35d0cc178..45d88942644be 100644 --- a/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts +++ b/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts @@ -44,8 +44,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not render', async () => { await pageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); }); diff --git a/x-pack/test/functional/apps/upgrade_assistant/feature_controls/upgrade_assistant_security.ts b/x-pack/test/functional/apps/upgrade_assistant/feature_controls/upgrade_assistant_security.ts index efe97f40d1612..ea771cc60f3d9 100644 --- a/x-pack/test/functional/apps/upgrade_assistant/feature_controls/upgrade_assistant_security.ts +++ b/x-pack/test/functional/apps/upgrade_assistant/feature_controls/upgrade_assistant_security.ts @@ -36,8 +36,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not render the "Stack" section', async () => { await PageObjects.common.navigateToApp('management'); - const sections = (await managementMenu.getSections()).map((section) => section.sectionId); - expect(sections).to.eql(['insightsAndAlerting', 'kibana']); + const sections = await managementMenu.getSections(); + + const sectionIds = sections.map((section) => section.sectionId); + expect(sectionIds).to.eql(['data', 'insightsAndAlerting', 'kibana']); + + const dataSection = sections.find((section) => section.sectionId === 'data'); + expect(dataSection?.sectionLinks).to.eql(['data_quality']); }); }); diff --git a/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts b/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts index a2b73f597414a..e691f84d7bdc7 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts @@ -96,6 +96,7 @@ export default function ({ getService }: FtrProviderContext) { filesSharedImage: 0, savedObjectsManagement: 1, savedQueryManagement: 0, + dataQuality: 0, }); }); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index 8a7a788c87468..6005e30ff2565 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -67,7 +67,8 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'searchInferenceEndpoints', 'guidedOnboardingFeature', 'securitySolutionAssistant', - 'securitySolutionAttackDiscovery' + 'securitySolutionAttackDiscovery', + 'dataQuality' ) ); break; From 3034dc86a778d8acdf0240fe00f0354132f03bd7 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:56:18 +0300 Subject: [PATCH 09/84] [Cloud Security] Temporarily disabled rule creation for 3P findings (#196185) --- .../components/detection_rule_counter.tsx | 32 ++++++++++++++----- .../create_detection_rule_from_benchmark.ts | 10 +++++- ..._detection_rule_from_vulnerability.test.ts | 2 +- ...reate_detection_rule_from_vulnerability.ts | 10 ++++++ 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx b/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx index 01309ce334d3c..8c75496e04c7d 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx @@ -17,6 +17,7 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { useHistory } from 'react-router-dom'; import useSessionStorage from 'react-use/lib/useSessionStorage'; import { useQueryClient } from '@tanstack/react-query'; +import { i18n as kbnI18n } from '@kbn/i18n'; import { useFetchDetectionRulesAlertsStatus } from '../common/api/use_fetch_detection_rules_alerts_status'; import { useFetchDetectionRulesByTags } from '../common/api/use_fetch_detection_rules_by_tags'; import { RuleResponse } from '../common/types'; @@ -67,15 +68,30 @@ export const DetectionRuleCounter = ({ tags, createRuleFn }: DetectionRuleCounte }, [history]); const createDetectionRuleOnClick = useCallback(async () => { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, CREATE_DETECTION_RULE_FROM_FLYOUT); const startServices = { analytics, notifications, i18n, theme }; - setIsCreateRuleLoading(true); - const ruleResponse = await createRuleFn(http); - setIsCreateRuleLoading(false); - showCreateDetectionRuleSuccessToast(startServices, http, ruleResponse); - // Triggering a refetch of rules and alerts to update the UI - queryClient.invalidateQueries([DETECTION_ENGINE_RULES_KEY]); - queryClient.invalidateQueries([DETECTION_ENGINE_ALERTS_KEY]); + + try { + setIsCreateRuleLoading(true); + uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, CREATE_DETECTION_RULE_FROM_FLYOUT); + + const ruleResponse = await createRuleFn(http); + + setIsCreateRuleLoading(false); + showCreateDetectionRuleSuccessToast(startServices, http, ruleResponse); + + // Triggering a refetch of rules and alerts to update the UI + queryClient.invalidateQueries([DETECTION_ENGINE_RULES_KEY]); + queryClient.invalidateQueries([DETECTION_ENGINE_ALERTS_KEY]); + } catch (e) { + setIsCreateRuleLoading(false); + + notifications.toasts.addWarning({ + title: kbnI18n.translate('xpack.csp.detectionRuleCounter.alerts.createRuleErrorTitle', { + defaultMessage: 'Coming Soon', + }), + text: e.message, + }); + } }, [createRuleFn, http, analytics, notifications, i18n, theme, queryClient]); if (alertsIsError) return <>{'-'}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.ts index 0ce1b7d09e897..cd09f275aaf22 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.ts @@ -8,8 +8,8 @@ import { HttpSetup } from '@kbn/core/public'; import { LATEST_FINDINGS_RETENTION_POLICY } from '@kbn/cloud-security-posture-common'; import type { CspBenchmarkRule } from '@kbn/cloud-security-posture-common/schema/rules/latest'; +import { i18n } from '@kbn/i18n'; import { FINDINGS_INDEX_PATTERN } from '../../../../common/constants'; - import { createDetectionRule } from '../../../common/api/create_detection_rule'; import { generateBenchmarkRuleTags } from '../../../../common/utils/detection_rules'; @@ -63,6 +63,14 @@ export const createDetectionRuleFromBenchmarkRule = async ( http: HttpSetup, benchmarkRule: CspBenchmarkRule['metadata'] ) => { + if (!benchmarkRule.benchmark?.posture_type) { + throw new Error( + i18n.translate('xpack.csp.createDetectionRuleFromBenchmarkRule.createRuleErrorMessage', { + defaultMessage: 'Rule creation is currently only available for Elastic findings', + }) + ); + } + return await createDetectionRule({ http, rule: { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.test.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.test.ts index 7dd0982cc58b5..4558d78fb8cf9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.test.ts @@ -18,7 +18,7 @@ jest.mock('../../../common/utils/is_native_csp_finding', () => ({ isNativeCspFinding: jest.fn(), })); -describe('CreateDetectionRuleFromVulnerability', () => { +describe.skip('CreateDetectionRuleFromVulnerability', () => { describe('getVulnerabilityTags', () => { it('should return tags with CSP_RULE_TAG and vulnerability id', () => { const mockVulnerability = { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts index 804e89fad61d8..bf01180c38789 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts @@ -13,6 +13,7 @@ import { VULNERABILITIES_SEVERITY, } from '@kbn/cloud-security-posture-common'; import type { Vulnerability } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; +import { CSP_VULN_DATASET } from '../../../common/utils/get_vendor_name'; import { isNativeCspFinding } from '../../../common/utils/is_native_csp_finding'; import { VULNERABILITIES_INDEX_PATTERN } from '../../../../common/constants'; import { createDetectionRule } from '../../../common/api/create_detection_rule'; @@ -87,6 +88,15 @@ export const createDetectionRuleFromVulnerabilityFinding = async ( http: HttpSetup, vulnerabilityFinding: CspVulnerabilityFinding ) => { + if (vulnerabilityFinding.data_stream?.dataset !== CSP_VULN_DATASET) { + throw new Error( + i18n.translate( + 'xpack.csp.createDetectionRuleFromVulnerabilityFinding.createRuleErrorMessage', + { defaultMessage: 'Rule creation is currently only available for Elastic findings' } + ) + ); + } + const tags = getVulnerabilityTags(vulnerabilityFinding); const vulnerability = vulnerabilityFinding.vulnerability; From 5c2df6347d779f577946634e972d30224299079a Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Tue, 15 Oct 2024 03:17:59 -0700 Subject: [PATCH 10/84] [Response Ops][Rules] Add New Rule Form to Stack Management (#194655) ## Summary Enables and adds the new rule form to stack management. We are only going to turn this on for stack management for now until we are confident that this is fairly bug free. ### To test: 1. Switch `USE_NEW_RULE_FORM_FEATURE_FLAG` to true 2. Navigate to stack management -> rules list 3. Click "Create rule" 4. Assert the user is navigated to the new form 5. Create rule 6. Assert the user is navigated to the rule details page 7. Click "Edit" 8. Edit rule 9. Assert the user is navigated to the rule details page 10. Try editing a rule in the rules list and assert everything works as expected We should also make sure this rule form is not enabled in other solutions. ### 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: Elastic Machine Co-authored-by: Christos Nasikas --- .../update_rule/transform_update_rule_body.ts | 2 +- .../src/common/constants/rule_flapping.ts | 2 +- .../src/common/hooks/use_create_rule.ts | 3 +- .../src/common/hooks/use_load_connectors.ts | 4 +- .../use_load_rule_type_aad_template_fields.ts | 4 +- .../src/common/hooks/use_resolve_rule.ts | 4 +- .../src/common/hooks/use_update_rule.ts | 3 +- .../src/common/types/rule_types.ts | 2 - .../src/rule_form/constants.ts | 3 +- .../src/rule_form/create_rule_form.tsx | 22 ++- .../src/rule_form/edit_rule_form.tsx | 28 ++- .../hooks/use_load_dependencies.test.tsx | 49 +---- .../rule_form/hooks/use_load_dependencies.ts | 39 ++-- .../rule_actions/rule_actions.test.tsx | 12 +- .../rule_form/rule_actions/rule_actions.tsx | 13 +- .../rule_actions_alerts_filter.tsx | 1 + .../rule_actions_connectors_modal.tsx | 5 +- .../rule_actions/rule_actions_item.test.tsx | 2 +- .../rule_actions/rule_actions_item.tsx | 91 ++++++--- .../rule_definition/rule_alert_delay.test.tsx | 7 +- .../rule_definition/rule_alert_delay.tsx | 14 +- .../rule_definition/rule_definition.test.tsx | 69 ++++++- .../rule_definition/rule_definition.tsx | 39 ++-- .../rule_definition/rule_schedule.tsx | 3 - .../src/rule_form/rule_form.tsx | 29 ++- .../rule_form_state_reducer.test.tsx | 2 + .../rule_form_state_reducer.ts | 49 +++-- .../rule_form/rule_page/rule_page.test.tsx | 15 +- .../src/rule_form/rule_page/rule_page.tsx | 178 +++++++++++++----- .../rule_page/rule_page_footer.test.tsx | 50 ++++- .../rule_form/rule_page/rule_page_footer.tsx | 6 +- .../src/rule_form/translations.ts | 37 +++- .../src/rule_form/types.ts | 4 +- .../utils/get_authorized_consumers.ts | 3 - .../src/rule_form/utils/get_default_params.ts | 25 +++ .../src/rule_form/utils/index.ts | 1 + .../src/rule_form/validation/validate_form.ts | 49 ++--- .../rule_settings_flapping_form.tsx | 12 +- .../rule_settings_flapping_title_tooltip.tsx | 1 + .../src/routes/stack_rule_paths.ts | 5 + .../public/application.tsx | 2 - .../.storybook/decorator.tsx | 2 +- .../common/experimental_features.ts | 2 +- .../public/application/constants/index.ts | 2 + .../public/application/home.tsx | 1 + .../public/application/lib/breadcrumb.ts | 12 ++ .../public/application/lib/doc_title.ts | 10 + .../public/application/rules_app.tsx | 22 ++- .../sections/rule_details/components/rule.tsx | 1 + .../components/rule_definition.test.tsx | 4 + .../components/rule_definition.tsx | 26 ++- .../components/rule_details.test.tsx | 4 + .../rule_details/components/rule_details.tsx | 30 ++- .../components/rule_details_route.test.tsx | 5 + .../sections/rule_form/rule_form_route.tsx | 100 ++++++++++ .../rules_list/components/rules_list.tsx | 33 +++- .../common/get_experimental_features.test.tsx | 4 +- .../triggers_actions_ui/public/types.ts | 1 + .../functional_with_es_ssl/config.base.ts | 1 + .../test_serverless/functional/config.base.ts | 5 + 60 files changed, 857 insertions(+), 297 deletions(-) create mode 100644 packages/kbn-alerts-ui-shared/src/rule_form/utils/get_default_params.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_route.tsx diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts index 8f4e59d80458b..9a719c24076f7 100644 --- a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts @@ -54,6 +54,6 @@ export const transformUpdateRuleBody: RewriteResponseCase = ({ ...(uuid && { uuid }), }; }), - ...(alertDelay ? { alert_delay: alertDelay } : {}), + ...(alertDelay !== undefined ? { alert_delay: alertDelay } : {}), ...(flapping !== undefined ? { flapping: transformUpdateRuleFlapping(flapping) } : {}), }); diff --git a/packages/kbn-alerts-ui-shared/src/common/constants/rule_flapping.ts b/packages/kbn-alerts-ui-shared/src/common/constants/rule_flapping.ts index 49ea5a63b3fca..542bb055fd431 100644 --- a/packages/kbn-alerts-ui-shared/src/common/constants/rule_flapping.ts +++ b/packages/kbn-alerts-ui-shared/src/common/constants/rule_flapping.ts @@ -8,4 +8,4 @@ */ // Feature flag for frontend rule specific flapping in rule flyout -export const IS_RULE_SPECIFIC_FLAPPING_ENABLED = false; +export const IS_RULE_SPECIFIC_FLAPPING_ENABLED = true; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts index 4ee00a94b90ed..ebdfeeafbe2fd 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts @@ -10,10 +10,11 @@ import { useMutation } from '@tanstack/react-query'; import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser'; import { createRule, CreateRuleBody } from '../apis/create_rule'; +import { Rule } from '../types'; export interface UseCreateRuleProps { http: HttpStart; - onSuccess?: (formData: CreateRuleBody) => void; + onSuccess?: (rule: Rule) => void; onError?: (error: IHttpFetchError<{ message: string }>) => void; } diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.ts index 9ae876d06278b..8c93881762b1a 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.ts @@ -15,10 +15,11 @@ export interface UseLoadConnectorsProps { http: HttpStart; includeSystemActions?: boolean; enabled?: boolean; + cacheTime?: number; } export const useLoadConnectors = (props: UseLoadConnectorsProps) => { - const { http, includeSystemActions = false, enabled = true } = props; + const { http, includeSystemActions = false, enabled = true, cacheTime } = props; const queryFn = () => { return fetchConnectors({ http, includeSystemActions }); @@ -27,6 +28,7 @@ export const useLoadConnectors = (props: UseLoadConnectorsProps) => { const { data, isLoading, isFetching, isInitialLoading } = useQuery({ queryKey: ['useLoadConnectors', includeSystemActions], queryFn, + cacheTime, refetchOnWindowFocus: false, enabled, }); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.ts index fab6fd3336f2e..c9dbc6c75ff35 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.ts @@ -17,10 +17,11 @@ export interface UseLoadRuleTypeAadTemplateFieldProps { http: HttpStart; ruleTypeId?: string; enabled: boolean; + cacheTime?: number; } export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplateFieldProps) => { - const { http, ruleTypeId, enabled } = props; + const { http, ruleTypeId, enabled, cacheTime } = props; const queryFn = () => { if (!ruleTypeId) { @@ -43,6 +44,7 @@ export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplat description: getDescription(d.name, EcsFlat), })); }, + cacheTime, refetchOnWindowFocus: false, enabled, }); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts index fafd372dc3640..95c3ca6baad02 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts @@ -15,10 +15,11 @@ import { RuleFormData } from '../../rule_form'; export interface UseResolveProps { http: HttpStart; id?: string; + cacheTime?: number; } export const useResolveRule = (props: UseResolveProps) => { - const { id, http } = props; + const { id, http, cacheTime } = props; const queryFn = () => { if (id) { @@ -30,6 +31,7 @@ export const useResolveRule = (props: UseResolveProps) => { queryKey: ['useResolveRule', id], queryFn, enabled: !!id, + cacheTime, select: (rule): RuleFormData | null => { if (!rule) { return null; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts index 0e8199fc1cca2..5764b8128ef42 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts @@ -10,10 +10,11 @@ import { useMutation } from '@tanstack/react-query'; import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser'; import { updateRule, UpdateRuleBody } from '../apis/update_rule'; +import { Rule } from '../types'; export interface UseUpdateRuleProps { http: HttpStart; - onSuccess?: (formData: UpdateRuleBody) => void; + onSuccess?: (rule: Rule) => void; onError?: (error: IHttpFetchError<{ message: string }>) => void; } diff --git a/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts b/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts index 40498f1a27886..29eaf17552a2b 100644 --- a/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts +++ b/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts @@ -27,8 +27,6 @@ import { TypeRegistry } from '../type_registry'; export type { SanitizedRuleAction as RuleAction } from '@kbn/alerting-types'; -export type { Flapping } from '@kbn/alerting-types'; - export type RuleTypeWithDescription = RuleType & { description?: string }; export type RuleTypeIndexWithDescriptions = Map; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/constants.ts b/packages/kbn-alerts-ui-shared/src/rule_form/constants.ts index f557dc5ebdb42..a3748eeabe697 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/constants.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/constants.ts @@ -27,7 +27,7 @@ export const DEFAULT_FREQUENCY = { summary: false, }; -export const GET_DEFAULT_FORM_DATA = ({ +export const getDefaultFormData = ({ ruleTypeId, name, consumer, @@ -50,6 +50,7 @@ export const GET_DEFAULT_FORM_DATA = ({ ruleTypeId, name, actions, + alertDelay: { active: 1 }, }; }; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/create_rule_form.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/create_rule_form.tsx index fc96ae214a7a8..4399dc5239ec7 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/create_rule_form.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/create_rule_form.tsx @@ -12,7 +12,7 @@ import { EuiLoadingElastic } from '@elastic/eui'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { type RuleCreationValidConsumer } from '@kbn/rule-data-utils'; import type { RuleFormData, RuleFormPlugins } from './types'; -import { DEFAULT_VALID_CONSUMERS, GET_DEFAULT_FORM_DATA } from './constants'; +import { DEFAULT_VALID_CONSUMERS, getDefaultFormData } from './constants'; import { RuleFormStateProvider } from './rule_form_state'; import { useCreateRule } from '../common/hooks'; import { RulePage } from './rule_page'; @@ -24,6 +24,7 @@ import { } from './rule_form_errors'; import { useLoadDependencies } from './hooks/use_load_dependencies'; import { + getAvailableRuleTypes, getInitialConsumer, getInitialMultiConsumer, getInitialSchedule, @@ -42,7 +43,8 @@ export interface CreateRuleFormProps { shouldUseRuleProducer?: boolean; canShowConsumerSelection?: boolean; showMustacheAutocompleteSwitch?: boolean; - returnUrl: string; + onCancel?: () => void; + onSubmit?: (ruleId: string) => void; } export const CreateRuleForm = (props: CreateRuleFormProps) => { @@ -56,7 +58,8 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => { shouldUseRuleProducer = false, canShowConsumerSelection = true, showMustacheAutocompleteSwitch = false, - returnUrl, + onCancel, + onSubmit, } = props; const { http, docLinks, notifications, ruleTypeRegistry, i18n, theme } = plugins; @@ -64,8 +67,9 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => { const { mutate, isLoading: isSaving } = useCreateRule({ http, - onSuccess: ({ name }) => { + onSuccess: ({ name, id }) => { toasts.addSuccess(RULE_CREATE_SUCCESS_TEXT(name)); + onSubmit?.(id); }, onError: (error) => { const message = parseRuleCircuitBreakerErrorMessage( @@ -86,6 +90,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => { const { isInitialLoading, ruleType, + ruleTypes, ruleTypeModel, uiConfig, healthCheckError, @@ -153,7 +158,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
{ minimumScheduleInterval: uiConfig?.minimumScheduleInterval, selectedRuleTypeModel: ruleTypeModel, selectedRuleType: ruleType, + availableRuleTypes: getAvailableRuleTypes({ + consumer, + ruleTypes, + ruleTypeRegistry, + }).map(({ ruleType: rt }) => rt), validConsumers, flappingSettings, canShowConsumerSelection, @@ -185,7 +195,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => { }), }} > - +
); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/edit_rule_form.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/edit_rule_form.tsx index 6e92b94cc2e0d..917fc87420f9a 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/edit_rule_form.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/edit_rule_form.tsx @@ -24,17 +24,19 @@ import { RuleFormRuleTypeError, } from './rule_form_errors'; import { RULE_EDIT_ERROR_TEXT, RULE_EDIT_SUCCESS_TEXT } from './translations'; -import { parseRuleCircuitBreakerErrorMessage } from './utils'; +import { getAvailableRuleTypes, parseRuleCircuitBreakerErrorMessage } from './utils'; +import { DEFAULT_VALID_CONSUMERS, getDefaultFormData } from './constants'; export interface EditRuleFormProps { id: string; plugins: RuleFormPlugins; showMustacheAutocompleteSwitch?: boolean; - returnUrl: string; + onCancel?: () => void; + onSubmit?: (ruleId: string) => void; } export const EditRuleForm = (props: EditRuleFormProps) => { - const { id, plugins, returnUrl, showMustacheAutocompleteSwitch = false } = props; + const { id, plugins, showMustacheAutocompleteSwitch = false, onCancel, onSubmit } = props; const { http, notifications, docLinks, ruleTypeRegistry, i18n, theme, application } = plugins; const { toasts } = notifications; @@ -42,6 +44,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => { http, onSuccess: ({ name }) => { toasts.addSuccess(RULE_EDIT_SUCCESS_TEXT(name)); + onSubmit?.(id); }, onError: (error) => { const message = parseRuleCircuitBreakerErrorMessage( @@ -62,6 +65,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => { const { isInitialLoading, ruleType, + ruleTypes, ruleTypeModel, uiConfig, healthCheckError, @@ -156,17 +160,31 @@ export const EditRuleForm = (props: EditRuleFormProps) => { connectors, connectorTypes, aadTemplateFields, - formData: fetchedFormData, + formData: { + ...getDefaultFormData({ + ruleTypeId: fetchedFormData.ruleTypeId, + name: fetchedFormData.name, + consumer: fetchedFormData.consumer, + actions: fetchedFormData.actions, + }), + ...fetchedFormData, + }, id, plugins, minimumScheduleInterval: uiConfig?.minimumScheduleInterval, selectedRuleType: ruleType, selectedRuleTypeModel: ruleTypeModel, + availableRuleTypes: getAvailableRuleTypes({ + consumer: fetchedFormData.consumer, + ruleTypes, + ruleTypeRegistry, + }).map(({ ruleType: rt }) => rt), flappingSettings, + validConsumers: DEFAULT_VALID_CONSUMERS, showMustacheAutocompleteSwitch, }} > - + ); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx index 9d2ce3b6f1211..f0a14ac82e4a6 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx @@ -46,10 +46,6 @@ jest.mock('../../common/hooks/use_load_rule_type_aad_template_fields', () => ({ useLoadRuleTypeAadTemplateField: jest.fn(), })); -jest.mock('../utils/get_authorized_rule_types', () => ({ - getAvailableRuleTypes: jest.fn(), -})); - jest.mock('../../common/hooks/use_fetch_flapping_settings', () => ({ useFetchFlappingSettings: jest.fn(), })); @@ -63,7 +59,6 @@ const { useLoadRuleTypeAadTemplateField } = jest.requireMock( '../../common/hooks/use_load_rule_type_aad_template_fields' ); const { useLoadRuleTypesQuery } = jest.requireMock('../../common/hooks/use_load_rule_types_query'); -const { getAvailableRuleTypes } = jest.requireMock('../utils/get_authorized_rule_types'); const { useFetchFlappingSettings } = jest.requireMock( '../../common/hooks/use_fetch_flapping_settings' ); @@ -168,13 +163,6 @@ useLoadRuleTypesQuery.mockReturnValue({ }, }); -getAvailableRuleTypes.mockReturnValue([ - { - ruleType: indexThresholdRuleType, - ruleTypeModel: indexThresholdRuleTypeModel, - }, -]); - const mockConnector = { id: 'test-connector', name: 'Test', @@ -236,7 +224,7 @@ const toastsMock = jest.fn(); const ruleTypeRegistryMock: RuleTypeRegistryContract = { has: jest.fn(), register: jest.fn(), - get: jest.fn(), + get: jest.fn().mockReturnValue(indexThresholdRuleTypeModel), list: jest.fn(), }; @@ -272,6 +260,7 @@ describe('useLoadDependencies', () => { isLoading: false, isInitialLoading: false, ruleType: indexThresholdRuleType, + ruleTypes: [...ruleTypeIndex.values()], ruleTypeModel: indexThresholdRuleTypeModel, uiConfig: uiConfigMock, healthCheckError: null, @@ -317,39 +306,6 @@ describe('useLoadDependencies', () => { }); }); - test('should call getAvailableRuleTypes with the correct params', async () => { - const { result } = renderHook( - () => { - return useLoadDependencies({ - http: httpMock as unknown as HttpStart, - toasts: toastsMock as unknown as ToastsStart, - ruleTypeRegistry: ruleTypeRegistryMock, - validConsumers: ['stackAlerts', 'logs'], - consumer: 'logs', - capabilities: { - actions: { - show: true, - save: true, - execute: true, - }, - } as unknown as ApplicationStart['capabilities'], - }); - }, - { wrapper } - ); - - await waitFor(() => { - return expect(result.current.isInitialLoading).toEqual(false); - }); - - expect(getAvailableRuleTypes).toBeCalledWith({ - consumer: 'logs', - ruleTypeRegistry: ruleTypeRegistryMock, - ruleTypes: [indexThresholdRuleType], - validConsumers: ['stackAlerts', 'logs'], - }); - }); - test('should call resolve rule with the correct params', async () => { const { result } = renderHook( () => { @@ -377,6 +333,7 @@ describe('useLoadDependencies', () => { expect(useResolveRule).toBeCalledWith({ http: httpMock, id: 'test-rule-id', + cacheTime: 0, }); }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.ts b/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.ts index 5e0c52b1089ba..9fb0f173b9d21 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.ts @@ -20,7 +20,6 @@ import { useLoadUiConfig, useResolveRule, } from '../../common/hooks'; -import { getAvailableRuleTypes } from '../utils'; import { RuleTypeRegistryContract } from '../../common'; import { useFetchFlappingSettings } from '../../common/hooks/use_fetch_flapping_settings'; import { IS_RULE_SPECIFIC_FLAPPING_ENABLED } from '../../common/constants/rule_flapping'; @@ -43,8 +42,6 @@ export const useLoadDependencies = (props: UseLoadDependencies) => { http, toasts, ruleTypeRegistry, - consumer, - validConsumers, id, ruleTypeId, capabilities, @@ -69,7 +66,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => { data: fetchedFormData, isLoading: isLoadingRule, isInitialLoading: isInitialLoadingRule, - } = useResolveRule({ http, id }); + } = useResolveRule({ http, id, cacheTime: 0 }); const { ruleTypesState: { @@ -100,6 +97,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => { http, includeSystemActions: true, enabled: canReadConnectors, + cacheTime: 0, }); const computedRuleTypeId = useMemo(() => { @@ -125,28 +123,22 @@ export const useLoadDependencies = (props: UseLoadDependencies) => { http, ruleTypeId: computedRuleTypeId, enabled: !!computedRuleTypeId && canReadConnectors, + cacheTime: 0, }); - const authorizedRuleTypeItems = useMemo(() => { - const computedConsumer = consumer || fetchedFormData?.consumer; - if (!computedConsumer) { - return []; + const ruleType = useMemo(() => { + if (!computedRuleTypeId || !ruleTypeIndex) { + return null; } - return getAvailableRuleTypes({ - consumer: computedConsumer, - ruleTypes: [...ruleTypeIndex.values()], - ruleTypeRegistry, - validConsumers, - }); - }, [consumer, ruleTypeIndex, ruleTypeRegistry, validConsumers, fetchedFormData]); - - const [ruleType, ruleTypeModel] = useMemo(() => { - const item = authorizedRuleTypeItems.find(({ ruleType: rt }) => { - return rt.id === computedRuleTypeId; - }); - - return [item?.ruleType, item?.ruleTypeModel]; - }, [authorizedRuleTypeItems, computedRuleTypeId]); + return ruleTypeIndex.get(computedRuleTypeId); + }, [computedRuleTypeId, ruleTypeIndex]); + + const ruleTypeModel = useMemo(() => { + if (!computedRuleTypeId) { + return null; + } + return ruleTypeRegistry.get(computedRuleTypeId); + }, [computedRuleTypeId, ruleTypeRegistry]); const isLoading = useMemo(() => { // Create Mode @@ -227,6 +219,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => { isInitialLoading: !!isInitialLoading, ruleType, ruleTypeModel, + ruleTypes: [...ruleTypeIndex.values()], uiConfig, healthCheckError, fetchedFormData, diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.test.tsx index 63846fb3628ce..9560d933060f6 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.test.tsx @@ -117,12 +117,18 @@ describe('ruleActions', () => { getActionTypeModel('1', { id: 'actionType-1', validateParams: mockValidate, + defaultActionParams: { + key: 'value', + }, }) ); actionTypeRegistry.register( getActionTypeModel('2', { id: 'actionType-2', validateParams: mockValidate, + defaultActionParams: { + key: 'value', + }, }) ); @@ -150,6 +156,10 @@ describe('ruleActions', () => { selectedRuleType: { id: 'selectedRuleTypeId', defaultActionGroupId: 'test', + recoveryActionGroup: { + id: 'test-recovery-group-id', + name: 'test-recovery-group', + }, producer: 'stackAlerts', }, connectors: mockConnectors, @@ -222,7 +232,7 @@ describe('ruleActions', () => { frequency: { notifyWhen: 'onActionGroupChange', summary: false, throttle: null }, group: 'test', id: 'connector-1', - params: {}, + params: { key: 'value' }, uuid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', }, type: 'addAction', diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.tsx index b9eb28025205c..47588b487be6d 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions.tsx @@ -18,6 +18,7 @@ import { ActionConnector, RuleAction, RuleFormParamsErrors } from '../../common/ import { DEFAULT_FREQUENCY, MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; import { RuleActionsItem } from './rule_actions_item'; import { RuleActionsSystemActionsItem } from './rule_actions_system_actions_item'; +import { getDefaultParams } from '../utils'; export const RuleActions = () => { const [isConnectorModalOpen, setIsConnectorModalOpen] = useState(false); @@ -44,7 +45,15 @@ export const RuleActions = () => { async (connector: ActionConnector) => { const { id, actionTypeId } = connector; const uuid = uuidv4(); - const params = {}; + const group = selectedRuleType.defaultActionGroupId; + const actionTypeModel = actionTypeRegistry.get(actionTypeId); + + const params = + getDefaultParams({ + group, + ruleType: selectedRuleType, + actionTypeModel, + }) || {}; dispatch({ type: 'addAction', @@ -53,7 +62,7 @@ export const RuleActions = () => { actionTypeId, uuid, params, - group: selectedRuleType.defaultActionGroupId, + group, frequency: DEFAULT_FREQUENCY, }, }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx index 791c1ce0491f2..a5bbacc74d7a5 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx @@ -68,6 +68,7 @@ export const RuleActionsAlertsFilter = ({ () => onChange(state ? undefined : query), [state, query, onChange] ); + const updateQuery = useCallback( (update: Partial) => { setQuery({ diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_connectors_modal.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_connectors_modal.tsx index 9c3dbcf15e364..82496d9578ff0 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_connectors_modal.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_connectors_modal.tsx @@ -163,7 +163,10 @@ export const RuleActionsConnectorsModal = (props: RuleActionsConnectorsModalProp const connectorFacetButtons = useMemo(() => { return ( - + { await userEvent.click(screen.getByText('onTimeframeChange')); - expect(mockOnChange).toHaveBeenCalledTimes(1); + expect(mockOnChange).toHaveBeenCalledTimes(2); expect(mockOnChange).toHaveBeenCalledWith({ payload: { diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_item.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_item.tsx index b80a79a69cfcf..9bf6cac970b19 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_item.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_item.tsx @@ -40,17 +40,12 @@ import { isEmpty, some } from 'lodash'; import { css } from '@emotion/react'; import { SavedObjectAttribute } from '@kbn/core/types'; import { useRuleFormDispatch, useRuleFormState } from '../hooks'; -import { - ActionConnector, - ActionTypeModel, - RuleFormParamsErrors, - RuleTypeWithDescription, -} from '../../common/types'; +import { ActionConnector, RuleFormParamsErrors } from '../../common/types'; import { getAvailableActionVariables } from '../../action_variables'; import { validateAction, validateParamsForWarnings } from '../validation'; import { RuleActionsSettings } from './rule_actions_settings'; -import { getSelectedActionGroup } from '../utils'; +import { getDefaultParams, getSelectedActionGroup } from '../utils'; import { RuleActionsMessage } from './rule_actions_message'; import { ACTION_ERROR_TOOLTIP, @@ -60,6 +55,7 @@ import { TECH_PREVIEW_DESCRIPTION, TECH_PREVIEW_LABEL, } from '../translations'; +import { checkActionFormActionTypeEnabled } from '../utils/check_action_type_enabled'; const SUMMARY_GROUP_TITLE = i18n.translate('alertsUIShared.ruleActionsItem.summaryGroupTitle', { defaultMessage: 'Summary of alerts', @@ -83,22 +79,6 @@ const ACTION_TITLE = (connector: ActionConnector) => }, }); -const getDefaultParams = ({ - group, - ruleType, - actionTypeModel, -}: { - group: string; - actionTypeModel: ActionTypeModel; - ruleType: RuleTypeWithDescription; -}) => { - if (group === ruleType.recoveryActionGroup.id) { - return actionTypeModel.defaultRecoveredActionParams; - } else { - return actionTypeModel.defaultActionParams; - } -}; - export interface RuleActionsItemProps { action: RuleAction; index: number; @@ -178,6 +158,16 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => { ? aadTemplateFields : availableActionVariables; + const checkEnabledResult = useMemo(() => { + if (!actionType) { + return null; + } + return checkActionFormActionTypeEnabled( + actionType, + connectors.filter((c) => c.isPreconfigured) + ); + }, [actionType, connectors]); + const onDelete = (id: string) => { dispatch({ type: 'removeAction', payload: { uuid: id } }); }; @@ -381,16 +371,24 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => { ...action.alertsFilter, query, }; + + if (!newAlertsFilter.query) { + delete newAlertsFilter.query; + } + + const alertsFilter = isEmpty(newAlertsFilter) ? undefined : newAlertsFilter; + const newAction = { ...action, - alertsFilter: newAlertsFilter, + alertsFilter, }; + dispatch({ type: 'setActionProperty', payload: { uuid: action.uuid!, key: 'alertsFilter', - value: newAlertsFilter, + value: alertsFilter, }, }); validateActionBase(newAction); @@ -400,19 +398,33 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => { const onTimeframeChange = useCallback( (timeframe?: AlertsFilterTimeframe) => { + const newAlertsFilter = { + ...action.alertsFilter, + timeframe, + }; + + if (!newAlertsFilter.timeframe) { + delete newAlertsFilter.timeframe; + } + + const alertsFilter = isEmpty(newAlertsFilter) ? undefined : newAlertsFilter; + + const newAction = { + ...action, + alertsFilter, + }; + dispatch({ type: 'setActionProperty', payload: { uuid: action.uuid!, key: 'alertsFilter', - value: { - ...action.alertsFilter, - timeframe, - }, + value: alertsFilter, }, }); + validateActionBase(newAction); }, - [action, dispatch] + [action, dispatch, validateActionBase] ); const onUseAadTemplateFieldsChange = useCallback(() => { @@ -443,9 +455,25 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => { }, [action, storedActionParamsForAadToggle, dispatch]); const accordionContent = useMemo(() => { - if (!connector) { + if (!connector || !checkEnabledResult) { return null; } + + if (!checkEnabledResult.isEnabled) { + return ( + + {checkEnabledResult.messageCard} + + ); + } + return ( { templateFields, useDefaultMessage, warning, + checkEnabledResult, onNotifyWhenChange, onActionGroupChange, onAlertsFilterChange, diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.test.tsx index 7b12160c1dadd..327a0ba12634c 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.test.tsx @@ -74,17 +74,14 @@ describe('RuleAlertDelay', () => { expect(mockOnChange).not.toHaveBeenCalled(); }); - test('Should call onChange with null if empty string is typed', () => { + test('Should not call onChange if empty string is typed', () => { render(); fireEvent.change(screen.getByTestId('alertDelayInput'), { target: { value: '', }, }); - expect(mockOnChange).toHaveBeenCalledWith({ - type: 'setAlertDelay', - payload: null, - }); + expect(mockOnChange).not.toHaveBeenCalled(); }); test('Should display error when input is invalid', () => { diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx index 5b26c38232ab4..a79f1f5efe447 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx @@ -28,16 +28,8 @@ export const RuleAlertDelay = () => { const onAlertDelayChange = useCallback( (e: React.ChangeEvent) => { - if (!e.target.validity.valid) { - return; - } - const value = e.target.value; - if (value === '') { - dispatch({ - type: 'setAlertDelay', - payload: null, - }); - } else if (INTEGER_REGEX.test(value)) { + const value = e.target.value.trim(); + if (INTEGER_REGEX.test(value)) { const parsedValue = parseInt(value, 10); dispatch({ type: 'setAlertDelay', @@ -66,7 +58,7 @@ export const RuleAlertDelay = () => { { active: 5, }, notifyWhen: null, - consumer: 'stackAlerts', + consumer: 'alerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, canShowConsumerSelection: true, validConsumers: ['logs', 'stackAlerts'], + availableRuleTypes: [ruleType], }); render(); @@ -164,13 +167,16 @@ describe('Rule Definition', () => { active: 5, }, notifyWhen: null, - consumer: 'stackAlerts', + consumer: 'alerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: { ...ruleModel, documentationUrl: null, }, + availableRuleTypes: [ruleType], + validConsumers: ['logs', 'stackAlerts'], }); render(); @@ -191,6 +197,7 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, @@ -215,9 +222,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], canShowConsumerSelect: true, validConsumers: ['logs'], }); @@ -241,9 +250,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], canShowConsumerSelect: true, validConsumers: ['logs', 'observability'], }); @@ -267,9 +278,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], }); render(); @@ -292,9 +305,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], }); render(); @@ -326,9 +341,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], canShowConsumerSelection: true, validConsumers: ['logs', 'stackAlerts'], }); @@ -339,6 +356,48 @@ describe('Rule Definition', () => { expect(screen.getByTestId('ruleSettingsFlappingForm')).toBeInTheDocument(); }); + test('should hide flapping if the user does not have read access', async () => { + useRuleFormState.mockReturnValue({ + plugins: { + charts: {} as ChartsPluginSetup, + data: {} as DataPublicPluginStart, + dataViews: {} as DataViewsPublicPluginStart, + unifiedSearch: {} as UnifiedSearchPublicPluginStart, + docLinks: {} as DocLinksStart, + application: { + capabilities: { + rulesSettings: { + readFlappingSettingsUI: false, + writeFlappingSettingsUI: true, + }, + }, + }, + }, + formData: { + id: 'test-id', + params: {}, + schedule: { + interval: '1m', + }, + alertDelay: { + active: 5, + }, + notifyWhen: null, + consumer: 'stackAlerts', + ruleTypeId: '.es-query', + }, + selectedRuleType: ruleType, + selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], + canShowConsumerSelection: true, + validConsumers: ['logs', 'stackAlerts'], + }); + + render(); + + expect(screen.queryByTestId('ruleDefinitionFlappingFormGroup')).not.toBeInTheDocument(); + }); + test('should allow flapping to be changed', async () => { useRuleFormState.mockReturnValue({ plugins, @@ -353,9 +412,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], canShowConsumerSelection: true, validConsumers: ['logs', 'stackAlerts'], }); @@ -389,9 +450,11 @@ describe('Rule Definition', () => { }, notifyWhen: null, consumer: 'stackAlerts', + ruleTypeId: '.es-query', }, selectedRuleType: ruleType, selectedRuleTypeModel: ruleModel, + availableRuleTypes: [ruleType], canShowConsumerSelection: true, validConsumers: ['logs', 'stackAlerts'], }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx index 3b404edc5d029..997e666e8340f 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { Suspense, useMemo, useState, useCallback } from 'react'; +import React, { Suspense, useMemo, useState, useCallback, useEffect } from 'react'; import { EuiEmptyPrompt, EuiLoadingSpinner, @@ -47,7 +47,7 @@ import { RuleAlertDelay } from './rule_alert_delay'; import { RuleConsumerSelection } from './rule_consumer_selection'; import { RuleSchedule } from './rule_schedule'; import { useRuleFormState, useRuleFormDispatch } from '../hooks'; -import { MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; +import { ALERTING_FEATURE_ID, MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; import { getAuthorizedConsumers } from '../utils'; import { RuleSettingsFlappingTitleTooltip } from '../../rule_settings/rule_settings_flapping_title_tooltip'; import { RuleSettingsFlappingForm } from '../../rule_settings/rule_settings_flapping_form'; @@ -62,6 +62,7 @@ export const RuleDefinition = () => { metadata, selectedRuleType, selectedRuleTypeModel, + availableRuleTypes, validConsumers, canShowConsumerSelection = false, flappingSettings, @@ -70,29 +71,44 @@ export const RuleDefinition = () => { const { colorMode } = useEuiTheme(); const dispatch = useRuleFormDispatch(); + useEffect(() => { + // Need to do a dry run validating the params because the Missing Monitor Data rule type + // does not properly initialize the params + if (selectedRuleType.id === 'monitoring_alert_missing_monitoring_data') { + dispatch({ type: 'runValidation' }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const { charts, data, dataViews, unifiedSearch, docLinks, application } = plugins; const { capabilities: { rulesSettings }, } = application; - const { writeFlappingSettingsUI } = rulesSettings || {}; + const { readFlappingSettingsUI, writeFlappingSettingsUI } = rulesSettings || {}; - const { params, schedule, notifyWhen, flapping } = formData; + const { params, schedule, notifyWhen, flapping, consumer, ruleTypeId } = formData; const [isAdvancedOptionsVisible, setIsAdvancedOptionsVisible] = useState(false); const [isFlappingPopoverOpen, setIsFlappingPopoverOpen] = useState(false); const authorizedConsumers = useMemo(() => { - if (!validConsumers?.length) { + if (consumer !== ALERTING_FEATURE_ID) { + return []; + } + const selectedAvailableRuleType = availableRuleTypes.find((ruleType) => { + return ruleType.id === selectedRuleType.id; + }); + if (!selectedAvailableRuleType?.authorizedConsumers) { return []; } return getAuthorizedConsumers({ - ruleType: selectedRuleType, + ruleType: selectedAvailableRuleType, validConsumers, }); - }, [selectedRuleType, validConsumers]); + }, [consumer, selectedRuleType, availableRuleTypes, validConsumers]); const shouldShowConsumerSelect = useMemo(() => { if (!canShowConsumerSelection) { @@ -107,10 +123,8 @@ export const RuleDefinition = () => { ) { return false; } - return ( - selectedRuleTypeModel.id && MULTI_CONSUMER_RULE_TYPE_IDS.includes(selectedRuleTypeModel.id) - ); - }, [authorizedConsumers, selectedRuleTypeModel, canShowConsumerSelection]); + return !!(ruleTypeId && MULTI_CONSUMER_RULE_TYPE_IDS.includes(ruleTypeId)); + }, [ruleTypeId, authorizedConsumers, canShowConsumerSelection]); const RuleParamsExpressionComponent = selectedRuleTypeModel.ruleParamsExpression ?? null; @@ -305,8 +319,9 @@ export const RuleDefinition = () => { > - {IS_RULE_SPECIFIC_FLAPPING_ENABLED && ( + {IS_RULE_SPECIFIC_FLAPPING_ENABLED && readFlappingSettingsUI && ( {ALERT_FLAPPING_DETECTION_TITLE}} description={ diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx index 26342d99580a6..1768303c55223 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx @@ -80,9 +80,6 @@ export const RuleSchedule = () => { const onIntervalNumberChange = useCallback( (e: React.ChangeEvent) => { - if (!e.target.validity.valid) { - return; - } const value = e.target.value.trim(); if (INTEGER_REGEX.test(value)) { const parsedValue = parseInt(value, 10); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form.tsx index d1a0f6a56fe2b..c09add5ae1c06 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form.tsx @@ -23,11 +23,12 @@ const queryClient = new QueryClient(); export interface RuleFormProps { plugins: RuleFormPlugins; - returnUrl: string; + onCancel?: () => void; + onSubmit?: (ruleId: string) => void; } export const RuleForm = (props: RuleFormProps) => { - const { plugins, returnUrl } = props; + const { plugins, onCancel, onSubmit } = props; const { id, ruleTypeId } = useParams<{ id?: string; ruleTypeId?: string; @@ -35,23 +36,31 @@ export const RuleForm = (props: RuleFormProps) => { const ruleFormComponent = useMemo(() => { if (id) { - return ; + return ; } if (ruleTypeId) { - return ; + return ( + + ); } return ( {RULE_FORM_ROUTE_PARAMS_ERROR_TITLE}} - > - -

{RULE_FORM_ROUTE_PARAMS_ERROR_TEXT}

-
-
+ body={ + +

{RULE_FORM_ROUTE_PARAMS_ERROR_TEXT}

+
+ } + /> ); - }, [id, ruleTypeId, plugins, returnUrl]); + }, [id, ruleTypeId, plugins, onCancel, onSubmit]); return {ruleFormComponent}; }; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx index 81d1aab4b2c3f..d8e6380462f9b 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx @@ -76,6 +76,8 @@ const initialState: RuleFormState = { selectedRuleType: indexThresholdRuleType, selectedRuleTypeModel: indexThresholdRuleTypeModel, multiConsumerSelection: 'stackAlerts', + availableRuleTypes: [], + validConsumers: [], connectors: [], connectorTypes: [], aadTemplateFields: [], diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.ts b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.ts index a65842125b6a8..d79ae00988875 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.ts @@ -8,7 +8,7 @@ */ import { RuleActionParams } from '@kbn/alerting-types'; -import { omit } from 'lodash'; +import { isEmpty, omit } from 'lodash'; import { RuleFormActionsErrors, RuleFormParamsErrors, RuleUiAction } from '../../common'; import { RuleFormData, RuleFormState } from '../types'; import { validateRuleBase, validateRuleParams } from '../validation'; @@ -106,13 +106,20 @@ export type RuleFormStateReducerAction = uuid: string; errors: RuleFormParamsErrors; }; + } + | { + type: 'runValidation'; }; const getUpdateWithValidation = (ruleFormState: RuleFormState) => (updater: () => RuleFormData): RuleFormState => { - const { minimumScheduleInterval, selectedRuleTypeModel, multiConsumerSelection } = - ruleFormState; + const { + minimumScheduleInterval, + selectedRuleTypeModel, + multiConsumerSelection, + selectedRuleType, + } = ruleFormState; const formData = updater(); @@ -121,17 +128,33 @@ const getUpdateWithValidation = ...(multiConsumerSelection ? { consumer: multiConsumerSelection } : {}), }; + const baseErrors = validateRuleBase({ + formData: formDataWithMultiConsumer, + minimumScheduleInterval, + }); + + const paramsErrors = validateRuleParams({ + formData: formDataWithMultiConsumer, + ruleTypeModel: selectedRuleTypeModel, + }); + + // We need to do this because the Missing Monitor Data rule type + // for whatever reason does not initialize the params with any data, + // therefore the expression component renders as blank + if (selectedRuleType.id === 'monitoring_alert_missing_monitoring_data') { + if (isEmpty(formData.params) && !isEmpty(paramsErrors)) { + Object.keys(paramsErrors).forEach((key) => { + formData.params[key] = null; + }); + } + } + return { ...ruleFormState, formData, - baseErrors: validateRuleBase({ - formData: formDataWithMultiConsumer, - minimumScheduleInterval, - }), - paramsErrors: validateRuleParams({ - formData: formDataWithMultiConsumer, - ruleTypeModel: selectedRuleTypeModel, - }), + baseErrors, + paramsErrors, + touched: true, }; }; @@ -222,6 +245,7 @@ export const ruleFormStateReducer = ( return { ...ruleFormState, multiConsumerSelection: payload, + touched: true, }; } case 'setMetadata': { @@ -326,6 +350,9 @@ export const ruleFormStateReducer = ( }, }; } + case 'runValidation': { + return updateWithValidation(() => formData); + } default: { return ruleFormState; } diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.test.tsx index ca80c0b77aae3..ac07c580fbd49 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.test.tsx @@ -61,6 +61,8 @@ const formDataMock: RuleFormData = { }, }; +const onCancel = jest.fn(); + useRuleFormState.mockReturnValue({ plugins: { application: { @@ -84,7 +86,6 @@ useRuleFormState.mockReturnValue({ }); const onSave = jest.fn(); -const returnUrl = 'management'; describe('rulePage', () => { afterEach(() => { @@ -92,7 +93,7 @@ describe('rulePage', () => { }); test('renders correctly', () => { - render(); + render(); expect(screen.getByText(RULE_FORM_PAGE_RULE_DEFINITION_TITLE)).toBeInTheDocument(); expect(screen.getByText(RULE_FORM_PAGE_RULE_ACTIONS_TITLE)).toBeInTheDocument(); @@ -100,7 +101,7 @@ describe('rulePage', () => { }); test('should call onSave when save button is pressed', () => { - render(); + render(); fireEvent.click(screen.getByTestId('rulePageFooterSaveButton')); fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); @@ -112,16 +113,16 @@ describe('rulePage', () => { }); test('should call onCancel when the cancel button is clicked', () => { - render(); + render(); fireEvent.click(screen.getByTestId('rulePageFooterCancelButton')); - expect(navigateToUrl).toHaveBeenCalledWith('management'); + expect(onCancel).toHaveBeenCalled(); }); test('should call onCancel when the return button is clicked', () => { - render(); + render(); fireEvent.click(screen.getByTestId('rulePageReturnButton')); - expect(navigateToUrl).toHaveBeenCalledWith('management'); + expect(onCancel).toHaveBeenCalled(); }); }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.tsx index 4e2e019d41269..68ff6d5db6b19 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiPageTemplate, EuiHorizontalRule, @@ -18,6 +18,8 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiCallOut, + EuiConfirmModal, } from '@elastic/eui'; import { RuleDefinition, @@ -33,32 +35,45 @@ import { RULE_FORM_PAGE_RULE_ACTIONS_TITLE, RULE_FORM_PAGE_RULE_DETAILS_TITLE, RULE_FORM_RETURN_TITLE, + DISABLED_ACTIONS_WARNING_TITLE, + RULE_FORM_CANCEL_MODAL_TITLE, + RULE_FORM_CANCEL_MODAL_DESCRIPTION, + RULE_FORM_CANCEL_MODAL_CONFIRM, + RULE_FORM_CANCEL_MODAL_CANCEL, } from '../translations'; +import { hasActionsError, hasActionsParamsErrors, hasParamsErrors } from '../validation'; +import { checkActionFormActionTypeEnabled } from '../utils/check_action_type_enabled'; export interface RulePageProps { isEdit?: boolean; isSaving?: boolean; - returnUrl: string; + onCancel?: () => void; onSave: (formData: RuleFormData) => void; } export const RulePage = (props: RulePageProps) => { - const { isEdit = false, isSaving = false, returnUrl, onSave } = props; + const { isEdit = false, isSaving = false, onCancel = () => {}, onSave } = props; + const [isCancelModalOpen, setIsCancelModalOpen] = useState(false); const { plugins: { application }, + baseErrors = {}, + paramsErrors = {}, + actionsErrors = {}, + actionsParamsErrors = {}, formData, multiConsumerSelection, + connectorTypes, + connectors, + touched, } = useRuleFormState(); + const { actions } = formData; + const canReadConnectors = !!application.capabilities.actions?.show; const styles = useEuiBackgroundColorCSS().transparent; - const onCancel = useCallback(() => { - application.navigateToUrl(returnUrl); - }, [application, returnUrl]); - const onSaveInternal = useCallback(() => { onSave({ ...formData, @@ -66,11 +81,51 @@ export const RulePage = (props: RulePageProps) => { }); }, [onSave, formData, multiConsumerSelection]); - const actionComponent = useMemo(() => { + const onCancelInternal = useCallback(() => { + if (touched) { + setIsCancelModalOpen(true); + } else { + onCancel(); + } + }, [touched, onCancel]); + + const hasActionsDisabled = useMemo(() => { + const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured); + return actions.some((action) => { + const actionType = connectorTypes.find(({ id }) => id === action.actionTypeId); + if (!actionType) { + return false; + } + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionType, + preconfiguredConnectors + ); + return !actionType.enabled && !checkEnabledResult.isEnabled; + }); + }, [actions, connectors, connectorTypes]); + + const hasRuleDefinitionErrors = useMemo(() => { + return !!( + hasParamsErrors(paramsErrors) || + baseErrors.interval?.length || + baseErrors.alertDelay?.length + ); + }, [paramsErrors, baseErrors]); + + const hasActionErrors = useMemo(() => { + return hasActionsError(actionsErrors) || hasActionsParamsErrors(actionsParamsErrors); + }, [actionsErrors, actionsParamsErrors]); + + const hasRuleDetailsError = useMemo(() => { + return baseErrors.name?.length || baseErrors.tags?.length; + }, [baseErrors]); + + const actionComponent: EuiStepsProps['steps'] = useMemo(() => { if (canReadConnectors) { return [ { title: RULE_FORM_PAGE_RULE_ACTIONS_TITLE, + status: hasActionErrors ? 'danger' : undefined, children: ( <> @@ -82,17 +137,19 @@ export const RulePage = (props: RulePageProps) => { ]; } return []; - }, [canReadConnectors]); + }, [hasActionErrors, canReadConnectors]); const steps: EuiStepsProps['steps'] = useMemo(() => { return [ { title: RULE_FORM_PAGE_RULE_DEFINITION_TITLE, + status: hasRuleDefinitionErrors ? 'danger' : undefined, children: , }, ...actionComponent, { title: RULE_FORM_PAGE_RULE_DETAILS_TITLE, + status: hasRuleDetailsError ? 'danger' : undefined, children: ( <> @@ -102,46 +159,73 @@ export const RulePage = (props: RulePageProps) => { ), }, ]; - }, [actionComponent]); + }, [hasRuleDefinitionErrors, hasRuleDetailsError, actionComponent]); return ( - - - + + + + + + {RULE_FORM_RETURN_TITLE} + + + + + + + + + + {hasActionsDisabled && ( + <> + + + + )} + + + + + + + {isCancelModalOpen && ( + setIsCancelModalOpen(false)} + onConfirm={onCancel} + buttonColor="danger" + defaultFocusedButton="confirm" + title={RULE_FORM_CANCEL_MODAL_TITLE} + confirmButtonText={RULE_FORM_CANCEL_MODAL_CONFIRM} + cancelButtonText={RULE_FORM_CANCEL_MODAL_CANCEL} > - - - {RULE_FORM_RETURN_TITLE} - - - - - - - - - - - - - - - +

{RULE_FORM_CANCEL_MODAL_DESCRIPTION}

+ + )} + ); }; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.test.tsx index 45e2008773583..d937c60aa3a52 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.test.tsx @@ -32,15 +32,27 @@ const onSave = jest.fn(); const onCancel = jest.fn(); hasRuleErrors.mockReturnValue(false); -useRuleFormState.mockReturnValue({ - baseErrors: {}, - paramsErrors: {}, - formData: { - actions: [], - }, -}); describe('rulePageFooter', () => { + beforeEach(() => { + useRuleFormState.mockReturnValue({ + plugins: { + application: { + capabilities: { + actions: { + show: true, + }, + }, + }, + }, + baseErrors: {}, + paramsErrors: {}, + formData: { + actions: [], + }, + }); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -75,6 +87,30 @@ describe('rulePageFooter', () => { expect(screen.getByTestId('rulePageConfirmCreateRule')).toBeInTheDocument(); }); + test('should not show creat rule confirmation if user cannot read actions', () => { + useRuleFormState.mockReturnValue({ + plugins: { + application: { + capabilities: { + actions: { + show: false, + }, + }, + }, + }, + baseErrors: {}, + paramsErrors: {}, + formData: { + actions: [], + }, + }); + + render(); + fireEvent.click(screen.getByTestId('rulePageFooterSaveButton')); + expect(screen.queryByTestId('rulePageConfirmCreateRule')).not.toBeInTheDocument(); + expect(onSave).toHaveBeenCalled(); + }); + test('should show call onSave if clicking rule confirmation', () => { render(); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.tsx index 09d2ac429fd50..62a0e4b64e4f1 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_page/rule_page_footer.tsx @@ -34,6 +34,7 @@ export const RulePageFooter = (props: RulePageFooterProps) => { const { isEdit = false, isSaving = false, onCancel, onSave } = props; const { + plugins: { application }, formData: { actions }, connectors, baseErrors = {}, @@ -78,11 +79,12 @@ export const RulePageFooter = (props: RulePageFooterProps) => { if (isEdit) { return onSave(); } - if (actions.length === 0) { + const canReadConnectors = !!application.capabilities.actions?.show; + if (actions.length === 0 && canReadConnectors) { return setShowCreateConfirmation(true); } onSave(); - }, [actions, isEdit, onSave]); + }, [actions, isEdit, application, onSave]); const onCreateConfirmClick = useCallback(() => { setShowCreateConfirmation(false); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/translations.ts b/packages/kbn-alerts-ui-shared/src/rule_form/translations.ts index 20e87c66f10f4..fca2e30b94434 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/translations.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/translations.ts @@ -194,7 +194,7 @@ export const RULE_TYPE_REQUIRED_TEXT = i18n.translate( export const RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT = i18n.translate( 'alertsUIShared.ruleForm.error.belowMinimumAlertDelayText', { - defaultMessage: 'Alert delay must be greater than 1.', + defaultMessage: 'Alert delay must be 1 or greater.', } ); @@ -498,6 +498,34 @@ export const RULE_FORM_RETURN_TITLE = i18n.translate('alertsUIShared.ruleForm.re defaultMessage: 'Return', }); +export const RULE_FORM_CANCEL_MODAL_TITLE = i18n.translate( + 'alertsUIShared.ruleForm.ruleFormCancelModalTitle', + { + defaultMessage: 'Discard unsaved changes to rule?', + } +); + +export const RULE_FORM_CANCEL_MODAL_DESCRIPTION = i18n.translate( + 'alertsUIShared.ruleForm.ruleFormCancelModalDescription', + { + defaultMessage: "You can't recover unsaved changes.", + } +); + +export const RULE_FORM_CANCEL_MODAL_CONFIRM = i18n.translate( + 'alertsUIShared.ruleForm.ruleFormCancelModalConfirm', + { + defaultMessage: 'Discard changes', + } +); + +export const RULE_FORM_CANCEL_MODAL_CANCEL = i18n.translate( + 'alertsUIShared.ruleForm.ruleFormCancelModalCancel', + { + defaultMessage: 'Cancel', + } +); + export const MODAL_SEARCH_PLACEHOLDER = i18n.translate( 'alertsUIShared.ruleForm.modalSearchPlaceholder', { @@ -586,3 +614,10 @@ export const TECH_PREVIEW_DESCRIPTION = i18n.translate( 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', } ); + +export const DISABLED_ACTIONS_WARNING_TITLE = i18n.translate( + 'alertsUIShared.disabledActionsWarningTitle', + { + defaultMessage: 'This rule has actions that are disabled', + } +); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/types.ts b/packages/kbn-alerts-ui-shared/src/rule_form/types.ts index d33c74da528db..4b45f64d3ead4 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/types.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/types.ts @@ -72,6 +72,7 @@ export interface RuleFormState { connectors: ActionConnector[]; connectorTypes: ActionType[]; aadTemplateFields: ActionVariable[]; + availableRuleTypes: RuleTypeWithDescription[]; baseErrors?: RuleFormBaseErrors; paramsErrors?: RuleFormParamsErrors; actionsErrors?: Record; @@ -83,8 +84,9 @@ export interface RuleFormState { metadata?: Record; minimumScheduleInterval?: MinimumScheduleInterval; canShowConsumerSelection?: boolean; - validConsumers?: RuleCreationValidConsumer[]; + validConsumers: RuleCreationValidConsumer[]; flappingSettings?: RulesSettingsFlapping; + touched?: boolean; } export type InitialRule = Partial & diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_authorized_consumers.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_authorized_consumers.ts index 217bb18328d0e..0b5234c669440 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_authorized_consumers.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_authorized_consumers.ts @@ -17,9 +17,6 @@ export const getAuthorizedConsumers = ({ ruleType: RuleTypeWithDescription; validConsumers: RuleCreationValidConsumer[]; }) => { - if (!ruleType.authorizedConsumers) { - return []; - } return Object.entries(ruleType.authorizedConsumers).reduce( (result, [authorizedConsumer, privilege]) => { if ( diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_default_params.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_default_params.ts new file mode 100644 index 0000000000000..d2aab787d6eb5 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_default_params.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import { ActionTypeModel, RuleTypeWithDescription } from '../../common/types'; + +export const getDefaultParams = ({ + group, + ruleType, + actionTypeModel, +}: { + group: string; + actionTypeModel: ActionTypeModel; + ruleType: RuleTypeWithDescription; +}) => { + if (group === ruleType.recoveryActionGroup.id) { + return actionTypeModel.defaultRecoveredActionParams; + } else { + return actionTypeModel.defaultActionParams; + } +}; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/index.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/index.ts index f5b583a1a9c63..53c9aedda7545 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/utils/index.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/index.ts @@ -17,3 +17,4 @@ export * from './get_initial_schedule'; export * from './has_fields_for_aad'; export * from './get_selected_action_group'; export * from './get_initial_consumer'; +export * from './get_default_params'; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/validation/validate_form.ts b/packages/kbn-alerts-ui-shared/src/rule_form/validation/validate_form.ts index d65e9c5893937..57afe66b53edf 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/validation/validate_form.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/validation/validate_form.ts @@ -35,7 +35,10 @@ export const validateAction = ({ action }: { action: RuleUiAction }): RuleFormAc if ('alertsFilter' in action) { const query = action?.alertsFilter?.query; - if (query && !query.kql) { + if (!query) { + return errors; + } + if (!query.filters.length && !query.kql) { errors.filterQuery.push( i18n.translate('alertsUIShared.ruleForm.actionsForm.requiredFilterQuery', { defaultMessage: 'A custom query is required.', @@ -43,7 +46,6 @@ export const validateAction = ({ action }: { action: RuleUiAction }): RuleFormAc ); } } - return errors; }; @@ -88,11 +90,7 @@ export function validateRuleBase({ errors.ruleTypeId.push(RULE_TYPE_REQUIRED_TEXT); } - if ( - formData.alertDelay && - !isNaN(formData.alertDelay?.active) && - formData.alertDelay?.active < 1 - ) { + if (!formData.alertDelay || isNaN(formData.alertDelay.active) || formData.alertDelay.active < 1) { errors.alertDelay.push(RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT); } @@ -111,34 +109,41 @@ export const validateRuleParams = ({ return ruleTypeModel.validate(formData.params, isServerless).errors; }; -const hasRuleBaseErrors = (errors: RuleFormBaseErrors) => { +export const hasRuleBaseErrors = (errors: RuleFormBaseErrors) => { return Object.values(errors).some((error: string[]) => error.length > 0); }; -const hasActionsError = (actionsErrors: Record) => { +export const hasActionsError = (actionsErrors: Record) => { return Object.values(actionsErrors).some((errors: RuleFormActionsErrors) => { return Object.values(errors).some((error: string[]) => error.length > 0); }); }; -const hasParamsErrors = (errors: RuleFormParamsErrors): boolean => { - const values = Object.values(errors); +export const hasParamsErrors = (errors: RuleFormParamsErrors | string | string[]): boolean => { let hasError = false; - for (const value of values) { - if (Array.isArray(value) && value.length > 0) { - return true; - } - if (typeof value === 'string' && value.trim() !== '') { - return true; - } - if (isObject(value)) { - hasError = hasParamsErrors(value as RuleFormParamsErrors); - } + + if (typeof errors === 'string' && errors.trim() !== '') { + hasError = true; } + + if (Array.isArray(errors)) { + errors.forEach((error) => { + hasError = hasError || hasParamsErrors(error); + }); + } + + if (isObject(errors)) { + Object.entries(errors).forEach(([_, value]) => { + hasError = hasError || hasParamsErrors(value); + }); + } + return hasError; }; -const hasActionsParamsErrors = (actionsParamsErrors: Record) => { +export const hasActionsParamsErrors = ( + actionsParamsErrors: Record +) => { return Object.values(actionsParamsErrors).some((errors: RuleFormParamsErrors) => { return hasParamsErrors(errors); }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_form.tsx b/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_form.tsx index 99f64f0a3977f..030cde8127b0a 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_form.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_form.tsx @@ -218,15 +218,17 @@ export const RuleSettingsFlappingForm = (props: RuleSettingsFlappingFormProps) = direction={isDesktop ? 'row' : 'column'} alignItems={isDesktop ? 'center' : undefined} > - + {flappingLabel} - + {enabled ? flappingOnLabel : flappingOffLabel} {flappingSettings && enabled && ( - {flappingOverrideLabel} + + {flappingOverrideLabel} + )} @@ -236,6 +238,7 @@ export const RuleSettingsFlappingForm = (props: RuleSettingsFlappingFormProps) = compressed checked={!!flappingSettings} label={flappingOverrideConfiguration} + disabled={!canWriteFlappingSettingsUI} onChange={onFlappingToggle} /> )} @@ -256,6 +259,7 @@ export const RuleSettingsFlappingForm = (props: RuleSettingsFlappingFormProps) = spaceFlappingSettings, flappingSettings, flappingOffTooltip, + canWriteFlappingSettingsUI, onFlappingToggle, ]); @@ -273,12 +277,14 @@ export const RuleSettingsFlappingForm = (props: RuleSettingsFlappingFormProps) = statusChangeThreshold={flappingSettings.statusChangeThreshold} onLookBackWindowChange={onLookBackWindowChange} onStatusChangeThresholdChange={onStatusChangeThresholdChange} + isDisabled={!canWriteFlappingSettingsUI} /> ); }, [ flappingSettings, spaceFlappingSettings, + canWriteFlappingSettingsUI, onLookBackWindowChange, onStatusChangeThresholdChange, ]); diff --git a/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_title_tooltip.tsx b/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_title_tooltip.tsx index 2a5cc4186013d..149eb5b792c1b 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_title_tooltip.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_settings/rule_settings_flapping_title_tooltip.tsx @@ -80,6 +80,7 @@ export const RuleSettingsFlappingTitleTooltip = (props: RuleSettingsFlappingTitl panelStyle={{ width: 500, }} + closePopover={() => setIsPopoverOpen(false)} button={ ruleDetailsRoute.replace(':ruleId', ruleId); +export const getCreateRuleRoute = (ruleTypeId: string) => + createRuleRoute.replace(':ruleTypeId', ruleTypeId); +export const getEditRuleRoute = (ruleId: string) => editRuleRoute.replace(':id', ruleId); diff --git a/x-pack/examples/triggers_actions_ui_example/public/application.tsx b/x-pack/examples/triggers_actions_ui_example/public/application.tsx index 4a429fbfd58d7..b3c11beb5285c 100644 --- a/x-pack/examples/triggers_actions_ui_example/public/application.tsx +++ b/x-pack/examples/triggers_actions_ui_example/public/application.tsx @@ -203,7 +203,6 @@ const TriggersActionsUiExampleApp = ({ ruleTypeRegistry: triggersActionsUi.ruleTypeRegistry, actionTypeRegistry: triggersActionsUi.actionTypeRegistry, }} - returnUrl={application.getUrlForApp('triggersActionsUiExample')} /> )} @@ -229,7 +228,6 @@ const TriggersActionsUiExampleApp = ({ ruleTypeRegistry: triggersActionsUi.ruleTypeRegistry, actionTypeRegistry: triggersActionsUi.actionTypeRegistry, }} - returnUrl={application.getUrlForApp('triggersActionsUiExample')} /> )} diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx index 183b1acd3ca53..233b673353929 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx +++ b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx @@ -69,7 +69,7 @@ export const StorybookContextDecorator: FC; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 8a6003960473b..8d39d7851d9bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -25,6 +25,8 @@ export const routeToConnectors = `/connectors`; export const routeToConnectorEdit = `/connectors/:connectorId`; export const routeToRules = `/rules`; export const routeToLogs = `/logs`; +export const routeToCreateRule = '/rules/create'; +export const routeToEditRule = '/rules/edit'; export const legacyRouteToAlerts = `/alerts`; export const legacyRouteToRuleDetails = `/alert/:alertId`; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 045732830c891..a2a2187c75895 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -72,6 +72,7 @@ export const TriggersActionsUIHome: React.FunctionComponent { defaultMessage: 'Rules', }); break; + case 'createRule': + updatedTitle = i18n.translate('xpack.triggersActionsUI.rules.createRule.breadcrumbTitle', { + defaultMessage: 'Create rule', + }); + break; + case 'editRule': + updatedTitle = i18n.translate('xpack.triggersActionsUI.rules.editRule.breadcrumbTitle', { + defaultMessage: 'Edit rule', + }); + break; case 'alerts': updatedTitle = i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { defaultMessage: 'Alerts', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/rules_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/rules_app.tsx index 9f472c251a91b..8550518edb457 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/rules_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/rules_app.tsx @@ -31,7 +31,7 @@ import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; -import { ruleDetailsRoute } from '@kbn/rule-data-utils'; +import { ruleDetailsRoute, createRuleRoute, editRuleRoute } from '@kbn/rule-data-utils'; import { QueryClientProvider } from '@tanstack/react-query'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; import { ExpressionsStart } from '@kbn/expressions-plugin/public'; @@ -54,11 +54,14 @@ import { KibanaContextProvider, useKibana } from '../common/lib/kibana'; import { ConnectorProvider } from './context/connector_context'; import { ALERTS_PAGE_ID, CONNECTORS_PLUGIN_ID } from '../common/constants'; import { queryClient } from './query_client'; +import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features'; const TriggersActionsUIHome = lazy(() => import('./home')); const RuleDetailsRoute = lazy( () => import('./sections/rule_details/components/rule_details_route') ); +const CreateRuleRoute = lazy(() => import('./sections/rule_form/rule_form_route')); +const EditRuleRoute = lazy(() => import('./sections/rule_form/rule_form_route')); export interface TriggersAndActionsUiServices extends CoreStart { actions: ActionsPublicPluginSetup; @@ -122,9 +125,25 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = application: { navigateToApp }, } = useKibana().services; + const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout'); + return ( + {!isUsingRuleCreateFlyout && ( + + )} + {!isUsingRuleCreateFlyout && ( + + )} - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx index c6598becec313..ca4de13be903b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx @@ -199,6 +199,7 @@ export function RuleComponent({ actionTypeRegistry, ruleTypeRegistry, hideEditButton: true, + useNewRuleForm: true, onEditRule: requestRefresh, })}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx index 91a77d18009c5..bc5da8218d118 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx @@ -21,6 +21,10 @@ jest.mock('./rule_actions', () => ({ }, })); +jest.mock('../../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), +})); + jest.mock('../../../lib/capabilities', () => ({ hasAllPrivilege: jest.fn(() => true), hasSaveRulesCapability: jest.fn(() => true), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx index e608a71af05a6..ed21bb88992a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx @@ -16,13 +16,14 @@ import { EuiLoadingSpinner, EuiDescriptionList, } from '@elastic/eui'; -import { AlertConsumers } from '@kbn/rule-data-utils'; +import { AlertConsumers, getEditRuleRoute, getRuleDetailsRoute } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; import { formatDuration } from '@kbn/alerting-plugin/common'; import { useLoadRuleTypesQuery } from '../../../hooks/use_load_rule_types_query'; import { RuleDefinitionProps } from '../../../../types'; import { RuleType } from '../../../..'; import { useKibana } from '../../../../common/lib/kibana'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { hasAllPrivilege, hasExecuteActionsCapability, @@ -38,11 +39,14 @@ export const RuleDefinition: React.FunctionComponent = ({ onEditRule, hideEditButton = false, filteredRuleTypes = [], + useNewRuleForm = false, }) => { const { - application: { capabilities }, + application: { capabilities, navigateToApp }, } = useKibana().services; + const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout'); + const [editFlyoutVisible, setEditFlyoutVisible] = useState(false); const [ruleType, setRuleType] = useState(); const { @@ -103,6 +107,20 @@ export const RuleDefinition: React.FunctionComponent = ({ return ''; }, [rule, ruleTypeRegistry]); + const onEditRuleClick = () => { + if (!isUsingRuleCreateFlyout && useNewRuleForm) { + navigateToApp('management', { + path: `insightsAndAlerting/triggersActions/${getEditRuleRoute(rule.id)}`, + state: { + returnApp: 'management', + returnPath: `insightsAndAlerting/triggersActions/${getRuleDetailsRoute(rule.id)}`, + }, + }); + } else { + setEditFlyoutVisible(true); + } + }; + const ruleDefinitionList = [ { title: i18n.translate('xpack.triggersActionsUI.ruleDetails.ruleType', { @@ -153,7 +171,7 @@ export const RuleDefinition: React.FunctionComponent = ({ > {hasEditButton ? ( - setEditFlyoutVisible(true)} flush="left"> + {getRuleConditionsWording()} ) : ( @@ -206,7 +224,7 @@ export const RuleDefinition: React.FunctionComponent = ({ setEditFlyoutVisible(true)} + onClick={onEditRuleClick} /> ) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index 615efb5ed74b6..ffde171117385 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -23,6 +23,10 @@ import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), +})); + jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ fetchUiConfig: jest .fn() diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index 8b2ee15db87d1..9422abdba3ec0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -26,7 +26,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common'; -import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; +import { getEditRuleRoute, getRuleDetailsRoute } from '@kbn/rule-data-utils'; import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; import { bulkUpdateAPIKey } from '../../../lib/rule_api/update_api_key'; @@ -71,6 +71,7 @@ import { import { useBulkOperationToast } from '../../../hooks/use_bulk_operation_toast'; import { RefreshToken } from './types'; import { UntrackAlertsModal } from '../../common/components/untrack_alerts_modal'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; export type RuleDetailsProps = { rule: Rule; @@ -78,6 +79,7 @@ export type RuleDetailsProps = { actionTypes: ActionType[]; requestRefresh: () => Promise; refreshToken?: RefreshToken; + useNewRuleForm?: boolean; } & Pick< BulkOperationsComponentOpts, 'bulkDisableRules' | 'bulkEnableRules' | 'bulkDeleteRules' | 'snoozeRule' | 'unsnoozeRule' @@ -98,7 +100,7 @@ export const RuleDetails: React.FunctionComponent = ({ }) => { const history = useHistory(); const { - application: { capabilities }, + application: { capabilities, navigateToApp }, ruleTypeRegistry, actionTypeRegistry, setBreadcrumbs, @@ -108,6 +110,9 @@ export const RuleDetails: React.FunctionComponent = ({ theme, notifications: { toasts }, } = useKibana().services; + + const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout'); + const ruleReducer = useMemo(() => getRuleReducer(actionTypeRegistry), [actionTypeRegistry]); const [{}, dispatch] = useReducer(ruleReducer, { rule }); const setInitialRule = (value: Rule) => { @@ -206,7 +211,7 @@ export const RuleDetails: React.FunctionComponent = ({ data-test-subj="ruleIntervalToastEditButton" onClick={() => { toasts.remove(configurationToast); - setEditFlyoutVisibility(true); + onEditRuleClick(); }} > = ({ }); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ i18nStart, theme, @@ -256,12 +262,26 @@ export const RuleDetails: React.FunctionComponent = ({ } }; + const onEditRuleClick = () => { + if (!isUsingRuleCreateFlyout) { + navigateToApp('management', { + path: `insightsAndAlerting/triggersActions/${getEditRuleRoute(rule.id)}`, + state: { + returnApp: 'management', + returnPath: `insightsAndAlerting/triggersActions/${getRuleDetailsRoute(rule.id)}`, + }, + }); + } else { + setEditFlyoutVisibility(true); + } + }; + const editButton = hasEditButton ? ( <> setEditFlyoutVisibility(true)} + onClick={onEditRuleClick} name="edit" disabled={!ruleType.enabledInLicense} > @@ -529,7 +549,7 @@ export const RuleDetails: React.FunctionComponent = ({ setEditFlyoutVisibility(true)} + onClick={onEditRuleClick} > ({ .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); + +jest.mock('../../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), +})); + describe('rule_details_route', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_route.tsx new file mode 100644 index 0000000000000..496b4d30873e6 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_route.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { RuleForm } from '@kbn/alerts-ui-shared/src/rule_form/rule_form'; +import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; +import { useLocation, useParams } from 'react-router-dom'; +import { useKibana } from '../../../common/lib/kibana'; +import { getAlertingSectionBreadcrumb } from '../../lib/breadcrumb'; +import { getCurrentDocTitle } from '../../lib/doc_title'; + +export const RuleFormRoute = () => { + const { + http, + i18n, + theme, + application, + notifications, + charts, + settings, + data, + dataViews, + unifiedSearch, + docLinks, + ruleTypeRegistry, + actionTypeRegistry, + chrome, + setBreadcrumbs, + } = useKibana().services; + + const location = useLocation<{ returnApp?: string; returnPath?: string }>(); + const { id, ruleTypeId } = useParams<{ + id?: string; + ruleTypeId?: string; + }>(); + const { returnApp, returnPath } = location.state || {}; + + // Set breadcrumb and page title + useEffect(() => { + if (id) { + setBreadcrumbs([ + getAlertingSectionBreadcrumb('rules', true), + getAlertingSectionBreadcrumb('editRule'), + ]); + chrome.docTitle.change(getCurrentDocTitle('editRule')); + } + if (ruleTypeId) { + setBreadcrumbs([ + getAlertingSectionBreadcrumb('rules', true), + getAlertingSectionBreadcrumb('createRule'), + ]); + chrome.docTitle.change(getCurrentDocTitle('createRule')); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + { + if (returnApp && returnPath) { + application.navigateToApp(returnApp, { path: returnPath }); + } else { + application.navigateToApp('management', { + path: `insightsAndAlerting/triggersActions/rules`, + }); + } + }} + onSubmit={(ruleId) => { + application.navigateToApp('management', { + path: `insightsAndAlerting/triggersActions/${getRuleDetailsRoute(ruleId)}`, + }); + }} + /> + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { RuleFormRoute as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index a36068125a6a5..d98aa2c5dec67 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -45,6 +45,8 @@ import { RuleCreationValidConsumer, ruleDetailsRoute as commonRuleDetailsRoute, STACK_ALERTS_FEATURE_ID, + getCreateRuleRoute, + getEditRuleRoute, } from '@kbn/rule-data-utils'; import { MaintenanceWindowCallout } from '@kbn/alerts-ui-shared'; import { @@ -139,6 +141,7 @@ export interface RulesListProps { onRefresh?: (refresh: Date) => void; setHeaderActions?: (components?: React.ReactNode[]) => void; initialSelectedConsumer?: RuleCreationValidConsumer | null; + useNewRuleForm?: boolean; } export const percentileFields = { @@ -180,12 +183,13 @@ export const RulesList = ({ onRefresh, setHeaderActions, initialSelectedConsumer = STACK_ALERTS_FEATURE_ID, + useNewRuleForm = false, }: RulesListProps) => { const history = useHistory(); const kibanaServices = useKibana().services; const { actionTypeRegistry, - application: { capabilities }, + application: { capabilities, navigateToApp }, http, kibanaFeatures, notifications: { toasts }, @@ -211,6 +215,7 @@ export const RulesList = ({ const cloneRuleId = useRef(null); const isRuleStatusFilterEnabled = getIsExperimentalFeatureEnabled('ruleStatusFilter'); + const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout'); const [percentileOptions, setPercentileOptions] = useState(initialPercentileOptions); @@ -312,8 +317,18 @@ export const RulesList = ({ }); const onRuleEdit = (ruleItem: RuleTableItem) => { - setEditFlyoutVisibility(true); - setCurrentRuleToEdit(ruleItem); + if (!isUsingRuleCreateFlyout && useNewRuleForm) { + navigateToApp('management', { + path: `insightsAndAlerting/triggersActions/${getEditRuleRoute(ruleItem.id)}`, + state: { + returnApp: 'management', + returnPath: `insightsAndAlerting/triggersActions/rules`, + }, + }); + } else { + setEditFlyoutVisibility(true); + setCurrentRuleToEdit(ruleItem); + } }; const onRunRule = async (id: string) => { @@ -1006,9 +1021,15 @@ export const RulesList = ({ setRuleTypeModalVisibility(false)} onSelectRuleType={(ruleTypeId) => { - setRuleTypeIdToCreate(ruleTypeId); - setRuleTypeModalVisibility(false); - setRuleFlyoutVisibility(true); + if (!isUsingRuleCreateFlyout) { + navigateToApp('management', { + path: `insightsAndAlerting/triggersActions/${getCreateRuleRoute(ruleTypeId)}`, + }); + } else { + setRuleTypeIdToCreate(ruleTypeId); + setRuleTypeModalVisibility(false); + setRuleFlyoutVisibility(true); + } }} http={http} toasts={toasts} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx index b7ffa1aa48b18..b33622423b92d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx @@ -24,7 +24,7 @@ describe('getIsExperimentalFeatureEnabled', () => { ruleKqlBar: true, isMustacheAutocompleteOn: false, showMustacheAutocompleteSwitch: false, - ruleFormV2: false, + isUsingRuleCreateFlyout: false, }, }); @@ -64,7 +64,7 @@ describe('getIsExperimentalFeatureEnabled', () => { expect(result).toEqual(false); - result = getIsExperimentalFeatureEnabled('ruleFormV2'); + result = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout'); expect(result).toEqual(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 6ad86397606c8..a592b19ae8a79 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -395,6 +395,7 @@ export interface RuleDefinitionProps Promise; hideEditButton?: boolean; filteredRuleTypes?: string[]; + useNewRuleForm?: boolean; } export enum Percentiles { diff --git a/x-pack/test/functional_with_es_ssl/config.base.ts b/x-pack/test/functional_with_es_ssl/config.base.ts index 2fdf49bc41fef..b4cc8a734a270 100644 --- a/x-pack/test/functional_with_es_ssl/config.base.ts +++ b/x-pack/test/functional_with_es_ssl/config.base.ts @@ -85,6 +85,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { 'stackAlertsPage', 'ruleTagFilter', 'ruleStatusFilter', + 'isUsingRuleCreateFlyout', ])}`, `--xpack.alerting.rules.minimumScheduleInterval.value="2s"`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index b0cd556fe1d36..1a3cd2ffd6a5b 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -36,6 +36,11 @@ export function createTestConfig(options: CreateTestConfigOptions) { serverArgs: [ ...svlSharedConfig.get('kbnTestServer.serverArgs'), `--serverless=${options.serverlessProject}`, + // Ensures the existing E2E tests are backwards compatible with the old rule create flyout + // Remove this experiment once all of the migration has been completed + `--xpack.trigger_actions_ui.enableExperimental=${JSON.stringify([ + 'isUsingRuleCreateFlyout', + ])}`, // custom native roles are enabled only for search and security projects ...(options.serverlessProject !== 'oblt' ? ['--xpack.security.roleManagementEnabled=true'] From 0ccfb70c810b037c5aa02270e5a59da284d2b31c Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Oct 2024 13:32:32 +0300 Subject: [PATCH 11/84] fix: [Stateful: Home page] Create an API key dialog information announcement duplication (#196133) Closes: #195754 Closes: #195252 ## Description Information about an element (in this case, a dialog) should be announced once to the user. If the user navigates to another element and then returns to the same dialog, they should hear the information about the dialog again (one time). ## What was changed?: 1. Added `aria-labelledby` for `EuiFlyout` based on the EUI recommendation. This will correctly pronounce the Flyout header without extra text. 2. Added `aria-labelledby` and `role="region"` for `EuiAccordion` for the same reason. ## Screen: image --- .../shared/api_key/create_api_key_flyout.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx index fe298fbd98f4b..38217df269fd1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx @@ -32,6 +32,7 @@ import { EuiSwitchEvent, EuiText, EuiTitle, + useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -161,6 +162,8 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose const apiKeyRef = useRef(null); + const uniqueId = useGeneratedHtmlId(); + useEffect(() => { if (createdApiKey && apiKeyRef) { apiKeyRef.current?.scrollIntoView(); @@ -178,10 +181,11 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose css={css` max-width: calc(${euiTheme.size.xxxxl} * 10); `} + aria-labelledby={`${uniqueId}-header`} > -

+

{i18n.translate('xpack.enterpriseSearch.apiKey.flyoutTitle', { defaultMessage: 'Create an API key', })} @@ -239,6 +243,8 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose id="apiKey.setup" paddingSize="l" initialIsOpen + aria-labelledby={`${uniqueId}-setupHeader`} + role="region" buttonContent={
@@ -247,7 +253,7 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose -

+

{i18n.translate('xpack.enterpriseSearch.apiKey.setup.title', { defaultMessage: 'Setup', })} @@ -283,6 +289,8 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose @@ -291,7 +299,7 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose -

+

{i18n.translate('xpack.enterpriseSearch.apiKey.privileges.title', { defaultMessage: 'Security Privileges', })} @@ -338,6 +346,8 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose @@ -346,7 +356,7 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose -

+

{i18n.translate('xpack.enterpriseSearch.apiKey.metadata.title', { defaultMessage: 'Metadata', })} From 2c1d5ce08fa55275148e61012aa49061f01c3dd9 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Oct 2024 13:33:30 +0300 Subject: [PATCH 12/84] fix: [Stateful: Home page] Not checked radio button receive focus a first element in radio group. (#195745) Closes: #195190 ## Description According to ARIA Authoring Practices Guide, focus should be on the checked radio button when the user reaches radio group while navigating using only keyboard. As of now, because all the time first radio button in the group receives focus, even if it is not checked, it may cause confusion and could potentially lead users to unintentionally change their selection without checking all checkboxes which exist in the group. ## What was changed: 1. Added name attribute for `EuiRadioGroup`. ## Screen: https://github.com/user-attachments/assets/20db2394-b9db-4c40-9e72-53ee860cd066 --- .../public/applications/shared/api_key/basic_setup_form.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/basic_setup_form.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/basic_setup_form.tsx index 0964f2909d85d..42a20a44dd06e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/basic_setup_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/basic_setup_form.tsx @@ -117,6 +117,7 @@ export const BasicSetupForm: React.FC = ({ 'data-test-subj': 'create-api-key-expires-days-radio', }, ]} + name="create-api-key-expires-group" idSelected={expires === null ? 'never' : 'days'} onChange={(id) => onChangeExpires(id === 'never' ? null : DEFAULT_EXPIRES_VALUE)} data-test-subj="create-api-key-expires-radio" From 422cad5c2dca04ed121544079be255ac85f9e479 Mon Sep 17 00:00:00 2001 From: Joe McElroy Date: Tue, 15 Oct 2024 11:44:17 +0100 Subject: [PATCH 13/84] [Onboarding] Small fixes from QA (#196178) ## Summary - update the code examples to use the normal client, not the elasticsearch client. The devtools team wants us to use the elasticsearch client here - update the code samples highlighting component so you can see highlighting --- .../public/code_examples/javascript.ts | 6 +-- .../public/code_examples/python.ts | 43 ++++++++----------- .../public/components/shared/code_sample.tsx | 5 +++ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/search_indices/public/code_examples/javascript.ts b/x-pack/plugins/search_indices/public/code_examples/javascript.ts index 3e91cb99301a7..a819b973388f4 100644 --- a/x-pack/plugins/search_indices/public/code_examples/javascript.ts +++ b/x-pack/plugins/search_indices/public/code_examples/javascript.ts @@ -19,7 +19,7 @@ export const JAVASCRIPT_INFO: CodeLanguage = { codeBlockLanguage: 'javascript', }; -const SERVERLESS_INSTALL_CMD = `npm install @elastic/elasticsearch-serverless`; +const SERVERLESS_INSTALL_CMD = `npm install @elastic/elasticsearch`; export const JavascriptServerlessCreateIndexExamples: CreateIndexLanguageExamples = { default: { @@ -28,7 +28,7 @@ export const JavascriptServerlessCreateIndexExamples: CreateIndexLanguageExample elasticsearchURL, apiKey, indexName, - }) => `import { Client } from "@elastic/elasticsearch-serverless" + }) => `import { Client } from "@elastic/elasticsearch" const client = new Client({ node: '${elasticsearchURL}', @@ -47,7 +47,7 @@ client.indices.create({ elasticsearchURL, apiKey, indexName, - }) => `import { Client } from "@elastic/elasticsearch-serverless" + }) => `import { Client } from "@elastic/elasticsearch" const client = new Client({ node: '${elasticsearchURL}', diff --git a/x-pack/plugins/search_indices/public/code_examples/python.ts b/x-pack/plugins/search_indices/public/code_examples/python.ts index e41e542456e72..ac405cfecd1e9 100644 --- a/x-pack/plugins/search_indices/public/code_examples/python.ts +++ b/x-pack/plugins/search_indices/public/code_examples/python.ts @@ -23,7 +23,7 @@ export const PYTHON_INFO: CodeLanguage = { codeBlockLanguage: 'python', }; -const SERVERLESS_PYTHON_INSTALL_CMD = 'pip install elasticsearch-serverless'; +const SERVERLESS_PYTHON_INSTALL_CMD = 'pip install elasticsearch'; export const PythonServerlessCreateIndexExamples: CreateIndexLanguageExamples = { default: { @@ -32,7 +32,7 @@ export const PythonServerlessCreateIndexExamples: CreateIndexLanguageExamples = elasticsearchURL, apiKey, indexName, - }: CodeSnippetParameters) => `from elasticsearch-serverless import Elasticsearch + }: CodeSnippetParameters) => `from elasticsearch import Elasticsearch client = Elasticsearch( "${elasticsearchURL}", @@ -49,21 +49,21 @@ client.indices.create( elasticsearchURL, apiKey, indexName, - }: CodeSnippetParameters) => `from elasticsearch-serverless import Elasticsearch + }: CodeSnippetParameters) => `from elasticsearch import Elasticsearch client = Elasticsearch( - "${elasticsearchURL}", - api_key="${apiKey ?? API_KEY_PLACEHOLDER}" + "${elasticsearchURL}", + api_key="${apiKey ?? API_KEY_PLACEHOLDER}" ) client.indices.create( - index="${indexName ?? INDEX_PLACEHOLDER}" - mappings={ - "properties": { - "vector": {"type": "dense_vector", "dims": 3 }, - "text": {"type": "text"} - } - } + index="${indexName ?? INDEX_PLACEHOLDER}", + mappings={ + "properties": { + "vector": {"type": "dense_vector", "dims": 3 }, + "text": {"type": "text"} + } + } )`, }, }; @@ -72,7 +72,7 @@ const serverlessIngestionCommand: IngestCodeSnippetFunction = ({ apiKey, indexName, sampleDocument, -}) => `from elasticsearch-serverless import Elasticsearch, helpers +}) => `from elasticsearch import Elasticsearch, helpers client = Elasticsearch( "${elasticsearchURL}", @@ -93,25 +93,20 @@ const serverlessUpdateMappingsCommand: IngestCodeSnippetFunction = ({ apiKey, indexName, mappingProperties, -}) => `from elasticsearch-serverless import Elasticsearch +}) => `from elasticsearch import Elasticsearch client = Elasticsearch( -"${elasticsearchURL}", -api_key="${apiKey ?? API_KEY_PLACEHOLDER}" + "${elasticsearchURL}", + api_key="${apiKey ?? API_KEY_PLACEHOLDER}" ) index_name = "${indexName}" mappings = ${JSON.stringify({ properties: mappingProperties }, null, 4)} -update_mapping_response = client.indices.put_mapping(index=index_name, body=mappings) - -# Print the response -print(update_mapping_response) - -# Verify the mapping -mapping = client.indices.get_mapping(index=index_name) -print(mapping)`; +mapping_response = client.indices.put_mapping(index=index_name, body=mappings) +print(mapping_response) +`; export const PythonServerlessVectorsIngestDataExample: IngestDataCodeDefinition = { installCommand: SERVERLESS_PYTHON_INSTALL_CMD, diff --git a/x-pack/plugins/search_indices/public/components/shared/code_sample.tsx b/x-pack/plugins/search_indices/public/components/shared/code_sample.tsx index 4ddce94d685b0..fc233e498ea10 100644 --- a/x-pack/plugins/search_indices/public/components/shared/code_sample.tsx +++ b/x-pack/plugins/search_indices/public/components/shared/code_sample.tsx @@ -54,6 +54,11 @@ export const CodeSample = ({ id, title, language, code, onCodeCopyClick }: CodeS paddingSize="m" isCopyable transparentBackground + css={{ + '*::selection': { + backgroundColor: 'rgba(255, 255, 255, 0.2)', + }, + }} > {code} From 32413591c381953e86d96a52efe9253785d43abf Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 15 Oct 2024 12:47:27 +0200 Subject: [PATCH 14/84] Skip serverless security agentless tests for MKI (#196250) ## Summary This PR skips the serverless security agentless test suite for MKI runs. Details in #196245 --- .../ftr/cloud_security_posture/agentless_api/create_agent.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts index b26581fb46dfd..8164fd39a81de 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts @@ -25,6 +25,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const AWS_SINGLE_ACCOUNT_TEST_ID = 'awsSingleTestId'; describe('Agentless API Serverless', function () { + // fails on MKI, see https://github.com/elastic/kibana/issues/196245 + this.tags(['failsOnMKI']); + let mockApiServer: http.Server; let cisIntegration: typeof pageObjects.cisAddIntegration; From 562bf21fbd805bf523748e692c177621fe93e133 Mon Sep 17 00:00:00 2001 From: Tre Date: Tue, 15 Oct 2024 12:40:10 +0100 Subject: [PATCH 15/84] [FTR][Ownership] Assign Ownership to "entity/*" ES Archives (#194436) ## Summary Modify code owner declarations for `x-pack/test/functional/es_archives/entity/**/*` in .github/CODEOWNERS ### For reviewers To verify this pr, you can use the `scripts/get_owners_for_file.js` script E.g: ``` node scripts/get_owners_for_file.js --file x-pack/test/functional/es_archives/entity//risks # Or any other file ``` Also, delete `x-pack/test/functional/es_archives/entity/user_risk` as `CMD+SHIFT+F` on my MAC in VS Code, resolved zero uses. #### Notes All of these are a best guess effort. The more help from the dev teams, the more accurate this will be for reporting in the future. Contributes to: https://github.com/elastic/kibana/issues/192979 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine Co-authored-by: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> --- .github/CODEOWNERS | 2 + .../es_archives/entity/user_risk/data.json | 39 ------------------- .../entity/user_risk/mappings.json | 35 ----------------- 3 files changed, 2 insertions(+), 74 deletions(-) delete mode 100644 x-pack/test/functional/es_archives/entity/user_risk/data.json delete mode 100644 x-pack/test/functional/es_archives/entity/user_risk/mappings.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 241593811f941..bd0fa1bc13104 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1750,6 +1750,8 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/common/api/detection_engine/signals_migration @elastic/security-detection-engine /x-pack/plugins/security_solution/common/cti @elastic/security-detection-engine /x-pack/plugins/security_solution/common/field_maps @elastic/security-detection-engine +/x-pack/test/functional/es_archives/entity/risks @elastic/security-detection-engine +/x-pack/test/functional/es_archives/entity/host_risk @elastic/security-detection-engine /x-pack/plugins/security_solution/public/sourcerer @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/detection_engine/rule_creation @elastic/security-detection-engine diff --git a/x-pack/test/functional/es_archives/entity/user_risk/data.json b/x-pack/test/functional/es_archives/entity/user_risk/data.json deleted file mode 100644 index 39b403deddc69..0000000000000 --- a/x-pack/test/functional/es_archives/entity/user_risk/data.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "type": "doc", - "value": { - "index": "ml_user_risk_score_latest_default", - "id": "1", - "source": { - "user": { - "name": "root", - "risk": { - "calculated_score_norm": 11, - "calculated_level": "Low" - } - }, - "ingest_timestamp": "2022-08-15T16:32:16.142561766Z", - "@timestamp": "2022-08-12T14:45:36.171Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "2", - "index": "ml_user_risk_score_latest_default", - "source": { - "host": { - "name": "User name 1", - "risk": { - "calculated_score_norm": 20, - "calculated_level": "Low" - } - }, - "ingest_timestamp": "2022-08-15T16:32:16.142561766Z", - "@timestamp": "2022-08-12T14:45:36.171Z" - }, - "type": "_doc" - } -} diff --git a/x-pack/test/functional/es_archives/entity/user_risk/mappings.json b/x-pack/test/functional/es_archives/entity/user_risk/mappings.json deleted file mode 100644 index 22518c9c455fc..0000000000000 --- a/x-pack/test/functional/es_archives/entity/user_risk/mappings.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - - "type": "index", - "value": { - "index": "ml_user_risk_score_latest_default", - "mappings": { - "properties": { - "user": { - "properties": { - "name": { - "type": "keyword" - }, - "risk": { - "properties": { - "calculated_level": { - "type": "keyword" - }, - "calculated_score_norm": { - "type": "float" - } - } - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} From c0bd82b30ca7e0fec99321412a37a2e37bc20970 Mon Sep 17 00:00:00 2001 From: Katerina Date: Tue, 15 Oct 2024 14:51:34 +0300 Subject: [PATCH 16/84] [Inventory][ECO] Show alerts for entities (#195250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Show alerts related to entities close https://github.com/elastic/kibana/issues/194381 ### Checklist - change default sorting from last seen to alertsCount - when alertsCount is not available server side sorting fallbacks to last seen - [Change app route from /app/observability/inventory to /app/inventory](https://github.com/elastic/kibana/pull/195250/commits/57598d05fbc27b5ef1c2654508719e4bd8069879) (causing issue when importing observability plugin - refactoring: move columns into seperate file https://github.com/user-attachments/assets/ea3abc5a-0581-41e7-a174-6655a39c1133 ### How to test - run any synthtrace scenario ex`node scripts/synthtrace infra_hosts_with_apm_hosts.ts` - create a rule (SLO or apm) - click on the alert count --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com> --- .../array/join_by_key.test.ts | 224 ++++++++++++++++++ .../observability_utils/array/join_by_key.ts | 60 +++++ .../inventory/common/entities.ts | 18 +- ...parse_identity_field_values_to_kql.test.ts | 90 +++++++ .../parse_identity_field_values_to_kql.ts | 34 +++ .../inventory/kibana.jsonc | 1 + .../alerts_badge/alerts_badge.test.tsx | 86 +++++++ .../components/alerts_badge/alerts_badge.tsx | 49 ++++ .../components/entities_grid/grid_columns.tsx | 113 +++++++++ .../public/components/entities_grid/index.tsx | 101 ++------ .../entities_grid/mock/entities_mock.ts | 6 + .../components/search_bar/discover_button.tsx | 3 +- .../public/pages/inventory_page/index.tsx | 4 +- .../inventory/public/plugin.ts | 2 +- .../inventory/public/routes/config.tsx | 7 +- .../create_alerts_client.ts | 47 ++++ .../entities/get_group_by_terms_agg.test.ts | 65 +++++ .../routes/entities/get_group_by_terms_agg.ts | 26 ++ .../entities/get_identify_fields.test.ts | 64 +++++ .../get_identity_fields_per_entity_type.ts | 21 ++ .../routes/entities/get_latest_entities.ts | 17 +- .../entities/get_latest_entities_alerts.ts | 65 +++++ .../inventory/server/routes/entities/route.ts | 48 +++- .../inventory/server/types.ts | 6 + .../inventory/tsconfig.json | 4 + 25 files changed, 1056 insertions(+), 105 deletions(-) create mode 100644 x-pack/packages/observability/observability_utils/array/join_by_key.test.ts create mode 100644 x-pack/packages/observability/observability_utils/array/join_by_key.ts create mode 100644 x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts create mode 100644 x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/server/lib/create_alerts_client.ts/create_alerts_client.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.test.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts diff --git a/x-pack/packages/observability/observability_utils/array/join_by_key.test.ts b/x-pack/packages/observability/observability_utils/array/join_by_key.test.ts new file mode 100644 index 0000000000000..8e0fc6ad09479 --- /dev/null +++ b/x-pack/packages/observability/observability_utils/array/join_by_key.test.ts @@ -0,0 +1,224 @@ +/* + * 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 { joinByKey } from './join_by_key'; + +describe('joinByKey', () => { + it('joins by a string key', () => { + const joined = joinByKey( + [ + { + serviceName: 'opbeans-node', + avg: 10, + }, + { + serviceName: 'opbeans-node', + count: 12, + }, + { + serviceName: 'opbeans-java', + avg: 11, + }, + { + serviceName: 'opbeans-java', + p95: 18, + }, + ], + 'serviceName' + ); + + expect(joined.length).toBe(2); + + expect(joined).toEqual([ + { + serviceName: 'opbeans-node', + avg: 10, + count: 12, + }, + { + serviceName: 'opbeans-java', + avg: 11, + p95: 18, + }, + ]); + }); + + it('joins by a record key', () => { + const joined = joinByKey( + [ + { + key: { + serviceName: 'opbeans-node', + transactionName: '/api/opbeans-node', + }, + avg: 10, + }, + { + key: { + serviceName: 'opbeans-node', + transactionName: '/api/opbeans-node', + }, + count: 12, + }, + { + key: { + serviceName: 'opbeans-java', + transactionName: '/api/opbeans-java', + }, + avg: 11, + }, + { + key: { + serviceName: 'opbeans-java', + transactionName: '/api/opbeans-java', + }, + p95: 18, + }, + ], + 'key' + ); + + expect(joined.length).toBe(2); + + expect(joined).toEqual([ + { + key: { + serviceName: 'opbeans-node', + transactionName: '/api/opbeans-node', + }, + avg: 10, + count: 12, + }, + { + key: { + serviceName: 'opbeans-java', + transactionName: '/api/opbeans-java', + }, + avg: 11, + p95: 18, + }, + ]); + }); + + it('joins by multiple keys', () => { + const data = [ + { + serviceName: 'opbeans-node', + environment: 'production', + type: 'service', + }, + { + serviceName: 'opbeans-node', + environment: 'stage', + type: 'service', + }, + { + serviceName: 'opbeans-node', + hostName: 'host-1', + }, + { + containerId: 'containerId', + }, + ]; + + const alerts = [ + { + serviceName: 'opbeans-node', + environment: 'production', + type: 'service', + alertCount: 10, + }, + { + containerId: 'containerId', + alertCount: 1, + }, + { + hostName: 'host-1', + environment: 'production', + alertCount: 5, + }, + ]; + + const joined = joinByKey( + [...data, ...alerts], + ['serviceName', 'environment', 'hostName', 'containerId'] + ); + + expect(joined.length).toBe(5); + + expect(joined).toEqual([ + { environment: 'stage', serviceName: 'opbeans-node', type: 'service' }, + { hostName: 'host-1', serviceName: 'opbeans-node' }, + { alertCount: 10, environment: 'production', serviceName: 'opbeans-node', type: 'service' }, + { alertCount: 1, containerId: 'containerId' }, + { alertCount: 5, environment: 'production', hostName: 'host-1' }, + ]); + }); + + it('uses the custom merge fn to replace items', () => { + const joined = joinByKey( + [ + { + serviceName: 'opbeans-java', + values: ['a'], + }, + { + serviceName: 'opbeans-node', + values: ['a'], + }, + { + serviceName: 'opbeans-node', + values: ['b'], + }, + { + serviceName: 'opbeans-node', + values: ['c'], + }, + ], + 'serviceName', + (a, b) => ({ + ...a, + ...b, + values: a.values.concat(b.values), + }) + ); + + expect(joined.find((item) => item.serviceName === 'opbeans-node')?.values).toEqual([ + 'a', + 'b', + 'c', + ]); + }); + + it('deeply merges objects', () => { + const joined = joinByKey( + [ + { + serviceName: 'opbeans-node', + properties: { + foo: '', + }, + }, + { + serviceName: 'opbeans-node', + properties: { + bar: '', + }, + }, + ], + 'serviceName' + ); + + expect(joined[0]).toEqual({ + serviceName: 'opbeans-node', + properties: { + foo: '', + bar: '', + }, + }); + }); +}); diff --git a/x-pack/packages/observability/observability_utils/array/join_by_key.ts b/x-pack/packages/observability/observability_utils/array/join_by_key.ts new file mode 100644 index 0000000000000..54e8ecdaf409b --- /dev/null +++ b/x-pack/packages/observability/observability_utils/array/join_by_key.ts @@ -0,0 +1,60 @@ +/* + * 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 { UnionToIntersection, ValuesType } from 'utility-types'; +import { merge, castArray } from 'lodash'; +import stableStringify from 'json-stable-stringify'; + +export type JoinedReturnType< + T extends Record, + U extends UnionToIntersection +> = Array< + Partial & { + [k in keyof T]: T[k]; + } +>; + +type ArrayOrSingle = T | T[]; + +export function joinByKey< + T extends Record, + U extends UnionToIntersection, + V extends ArrayOrSingle +>(items: T[], key: V): JoinedReturnType; + +export function joinByKey< + T extends Record, + U extends UnionToIntersection, + V extends ArrayOrSingle, + W extends JoinedReturnType, + X extends (a: T, b: T) => ValuesType +>(items: T[], key: V, mergeFn: X): W; + +export function joinByKey( + items: Array>, + key: string | string[], + mergeFn: Function = (a: Record, b: Record) => merge({}, a, b) +) { + const keys = castArray(key); + // Create a map to quickly query the key of group. + const map = new Map(); + items.forEach((current) => { + // The key of the map is a stable JSON string of the values from given keys. + // We need stable JSON string to support plain object values. + const stableKey = stableStringify(keys.map((k) => current[k])); + + if (map.has(stableKey)) { + const item = map.get(stableKey); + // delete and set the key to put it last + map.delete(stableKey); + map.set(stableKey, mergeFn(item, current)); + } else { + map.set(stableKey, { ...current }); + } + }); + return [...map.values()]; +} diff --git a/x-pack/plugins/observability_solution/inventory/common/entities.ts b/x-pack/plugins/observability_solution/inventory/common/entities.ts index 40fae48cb9dc3..7df71559aa97a 100644 --- a/x-pack/plugins/observability_solution/inventory/common/entities.ts +++ b/x-pack/plugins/observability_solution/inventory/common/entities.ts @@ -6,6 +6,9 @@ */ import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema'; import { + HOST_NAME, + SERVICE_ENVIRONMENT, + SERVICE_NAME, AGENT_NAME, CLOUD_PROVIDER, CONTAINER_ID, @@ -15,9 +18,6 @@ import { ENTITY_IDENTITY_FIELDS, ENTITY_LAST_SEEN, ENTITY_TYPE, - HOST_NAME, - SERVICE_ENVIRONMENT, - SERVICE_NAME, } from '@kbn/observability-shared-plugin/common'; import { isRight } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; @@ -28,8 +28,19 @@ export const entityTypeRt = t.union([ t.literal('container'), ]); +export const entityColumnIdsRt = t.union([ + t.literal(ENTITY_DISPLAY_NAME), + t.literal(ENTITY_LAST_SEEN), + t.literal(ENTITY_TYPE), + t.literal('alertsCount'), +]); + +export type EntityColumnIds = t.TypeOf; + export type EntityType = t.TypeOf; +export const defaultEntitySortField: EntityColumnIds = 'alertsCount'; + export const MAX_NUMBER_OF_ENTITIES = 500; export const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({ @@ -79,6 +90,7 @@ interface BaseEntity { [ENTITY_DISPLAY_NAME]: string; [ENTITY_DEFINITION_ID]: string; [ENTITY_IDENTITY_FIELDS]: string | string[]; + alertsCount?: number; [key: string]: any; } diff --git a/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts new file mode 100644 index 0000000000000..c4b48410456f8 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { + ENTITY_DEFINITION_ID, + ENTITY_DISPLAY_NAME, + ENTITY_ID, + ENTITY_LAST_SEEN, +} from '@kbn/observability-shared-plugin/common'; +import { HostEntity, ServiceEntity } from '../entities'; +import { parseIdentityFieldValuesToKql } from './parse_identity_field_values_to_kql'; + +const commonEntityFields = { + [ENTITY_LAST_SEEN]: '2023-10-09T00:00:00Z', + [ENTITY_ID]: '1', + [ENTITY_DISPLAY_NAME]: 'entity_name', + [ENTITY_DEFINITION_ID]: 'entity_definition_id', + alertCount: 3, +}; + +describe('parseIdentityFieldValuesToKql', () => { + it('should return the value when identityFields is a single string', () => { + const entity: ServiceEntity = { + 'agent.name': 'node', + 'entity.identityFields': 'service.name', + 'service.name': 'my-service', + 'entity.type': 'service', + ...commonEntityFields, + }; + + const result = parseIdentityFieldValuesToKql({ entity }); + expect(result).toEqual('service.name: "my-service"'); + }); + + it('should return values when identityFields is an array of strings', () => { + const entity: ServiceEntity = { + 'agent.name': 'node', + 'entity.identityFields': ['service.name', 'service.environment'], + 'service.name': 'my-service', + 'entity.type': 'service', + 'service.environment': 'staging', + ...commonEntityFields, + }; + + const result = parseIdentityFieldValuesToKql({ entity }); + expect(result).toEqual('service.name: "my-service" AND service.environment: "staging"'); + }); + + it('should return an empty string if identityFields is empty string', () => { + const entity: ServiceEntity = { + 'agent.name': 'node', + 'entity.identityFields': '', + 'service.name': 'my-service', + 'entity.type': 'service', + ...commonEntityFields, + }; + + const result = parseIdentityFieldValuesToKql({ entity }); + expect(result).toEqual(''); + }); + it('should return an empty array if identityFields is empty array', () => { + const entity: ServiceEntity = { + 'agent.name': 'node', + 'entity.identityFields': [], + 'service.name': 'my-service', + 'entity.type': 'service', + ...commonEntityFields, + }; + + const result = parseIdentityFieldValuesToKql({ entity }); + expect(result).toEqual(''); + }); + + it('should ignore fields that are not present in the entity', () => { + const entity: HostEntity = { + 'entity.identityFields': ['host.name', 'foo.bar'], + 'host.name': 'my-host', + 'entity.type': 'host', + 'cloud.provider': null, + ...commonEntityFields, + }; + + const result = parseIdentityFieldValuesToKql({ entity }); + expect(result).toEqual('host.name: "my-host"'); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.ts b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.ts new file mode 100644 index 0000000000000..2e3f3dadd4109 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.ts @@ -0,0 +1,34 @@ +/* + * 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 { ENTITY_IDENTITY_FIELDS } from '@kbn/observability-shared-plugin/common'; +import { Entity } from '../entities'; + +type Operator = 'AND'; +export function parseIdentityFieldValuesToKql({ + entity, + operator = 'AND', +}: { + entity: Entity; + operator?: Operator; +}) { + const mapping: string[] = []; + + const identityFields = entity[ENTITY_IDENTITY_FIELDS]; + + if (identityFields) { + const fields = [identityFields].flat(); + + fields.forEach((field) => { + if (field in entity) { + mapping.push(`${[field]}: "${entity[field as keyof Entity]}"`); + } + }); + } + + return mapping.join(` ${operator} `); +} diff --git a/x-pack/plugins/observability_solution/inventory/kibana.jsonc b/x-pack/plugins/observability_solution/inventory/kibana.jsonc index 1467d294a4f49..fc77163ae3c5f 100644 --- a/x-pack/plugins/observability_solution/inventory/kibana.jsonc +++ b/x-pack/plugins/observability_solution/inventory/kibana.jsonc @@ -16,6 +16,7 @@ "features", "unifiedSearch", "data", + "ruleRegistry", "share" ], "requiredBundles": ["kibanaReact"], diff --git a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx new file mode 100644 index 0000000000000..c60490c8a12b1 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { type KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; +import { render, screen } from '@testing-library/react'; +import { AlertsBadge } from './alerts_badge'; +import * as useKibana from '../../hooks/use_kibana'; +import { HostEntity, ServiceEntity } from '../../../common/entities'; + +describe('AlertsBadge', () => { + jest.spyOn(useKibana, 'useKibana').mockReturnValue({ + services: { + http: { + basePath: { + prepend: (path: string) => path, + }, + }, + }, + } as unknown as KibanaReactContextValue); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('render alerts badge for a host entity', () => { + const entity: HostEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'host', + 'entity.displayName': 'foo', + 'entity.identityFields': 'host.name', + 'host.name': 'foo', + 'entity.definitionId': 'host', + 'cloud.provider': null, + alertsCount: 1, + }; + render(); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual( + '/app/observability/alerts?_a=(kuery:\'host.name: "foo"\',status:active)' + ); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('1'); + }); + it('render alerts badge for a service entity', () => { + const entity: ServiceEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'agent.name': 'node', + 'entity.id': '1', + 'entity.type': 'service', + 'entity.displayName': 'foo', + 'entity.identityFields': 'service.name', + 'service.name': 'bar', + 'entity.definitionId': 'host', + 'cloud.provider': null, + alertsCount: 5, + }; + render(); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual( + '/app/observability/alerts?_a=(kuery:\'service.name: "bar"\',status:active)' + ); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('5'); + }); + it('render alerts badge for a service entity with multiple identity fields', () => { + const entity: ServiceEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'agent.name': 'node', + 'entity.id': '1', + 'entity.type': 'service', + 'entity.displayName': 'foo', + 'entity.identityFields': ['service.name', 'service.environment'], + 'service.name': 'bar', + 'service.environment': 'prod', + 'entity.definitionId': 'host', + 'cloud.provider': null, + alertsCount: 2, + }; + render(); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual( + '/app/observability/alerts?_a=(kuery:\'service.name: "bar" AND service.environment: "prod"\',status:active)' + ); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('2'); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx new file mode 100644 index 0000000000000..ba1b992ff62c1 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import rison from '@kbn/rison'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Entity } from '../../../common/entities'; +import { useKibana } from '../../hooks/use_kibana'; +import { parseIdentityFieldValuesToKql } from '../../../common/utils/parse_identity_field_values_to_kql'; + +export function AlertsBadge({ entity }: { entity: Entity }) { + const { + services: { + http: { basePath }, + }, + } = useKibana(); + + const activeAlertsHref = basePath.prepend( + `/app/observability/alerts?_a=${rison.encode({ + kuery: parseIdentityFieldValuesToKql({ entity }), + status: 'active', + })}` + ); + return ( + + + {entity.alertsCount} + + + ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx new file mode 100644 index 0000000000000..96fb8b3736ead --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonIcon, EuiDataGridColumn, EuiToolTip } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + ENTITY_DISPLAY_NAME, + ENTITY_LAST_SEEN, + ENTITY_TYPE, +} from '@kbn/observability-shared-plugin/common'; + +const alertsLabel = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.alertsLabel', { + defaultMessage: 'Alerts', +}); + +const alertsTooltip = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip', { + defaultMessage: 'The count of the active alerts', +}); + +const entityNameLabel = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel', { + defaultMessage: 'Entity name', +}); +const entityNameTooltip = i18n.translate( + 'xpack.inventory.entitiesGrid.euiDataGrid.entityNameTooltip', + { + defaultMessage: 'Name of the entity (entity.displayName)', + } +); + +const entityTypeLabel = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.typeLabel', { + defaultMessage: 'Type', +}); +const entityTypeTooltip = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip', { + defaultMessage: 'Type of entity (entity.type)', +}); + +const entityLastSeenLabel = i18n.translate( + 'xpack.inventory.entitiesGrid.euiDataGrid.lastSeenLabel', + { + defaultMessage: 'Last seen', + } +); +const entityLastSeenToolip = i18n.translate( + 'xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip', + { + defaultMessage: 'Timestamp of last received data for entity (entity.lastSeenTimestamp)', + } +); + +const CustomHeaderCell = ({ title, tooltipContent }: { title: string; tooltipContent: string }) => ( + <> + {title} + + + + +); + +export const getColumns = ({ + showAlertsColumn, +}: { + showAlertsColumn: boolean; +}): EuiDataGridColumn[] => { + return [ + ...(showAlertsColumn + ? [ + { + id: 'alertsCount', + displayAsText: alertsLabel, + isSortable: true, + display: , + initialWidth: 100, + schema: 'numeric', + }, + ] + : []), + { + id: ENTITY_DISPLAY_NAME, + // keep it for accessibility purposes + displayAsText: entityNameLabel, + display: , + isSortable: true, + }, + { + id: ENTITY_TYPE, + // keep it for accessibility purposes + displayAsText: entityTypeLabel, + display: , + isSortable: true, + }, + { + id: ENTITY_LAST_SEEN, + // keep it for accessibility purposes + displayAsText: entityLastSeenLabel, + display: ( + + ), + defaultSortDirection: 'desc', + isSortable: true, + schema: 'datetime', + }, + ]; +}; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx index 8bdfa0d46627c..697bc3304753e 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx @@ -5,103 +5,32 @@ * 2.0. */ import { - EuiButtonIcon, EuiDataGrid, EuiDataGridCellValueElementProps, - EuiDataGridColumn, EuiDataGridSorting, EuiLoadingSpinner, EuiText, - EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; import { last } from 'lodash'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { ENTITY_DISPLAY_NAME, ENTITY_LAST_SEEN, ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; +import { EntityColumnIds, EntityType } from '../../../common/entities'; import { APIReturnType } from '../../api'; import { BadgeFilterWithPopover } from '../badge_filter_with_popover'; +import { getColumns } from './grid_columns'; +import { AlertsBadge } from '../alerts_badge/alerts_badge'; import { EntityName } from './entity_name'; -import { EntityType } from '../../../common/entities'; import { getEntityTypeLabel } from '../../utils/get_entity_type_label'; type InventoryEntitiesAPIReturnType = APIReturnType<'GET /internal/inventory/entities'>; type LatestEntities = InventoryEntitiesAPIReturnType['entities']; -export type EntityColumnIds = - | typeof ENTITY_DISPLAY_NAME - | typeof ENTITY_LAST_SEEN - | typeof ENTITY_TYPE; - -const CustomHeaderCell = ({ title, tooltipContent }: { title: string; tooltipContent: string }) => ( - <> - {title} - - - - -); - -const entityNameLabel = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel', { - defaultMessage: 'Entity name', -}); -const entityTypeLabel = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.typeLabel', { - defaultMessage: 'Type', -}); -const entityLastSeenLabel = i18n.translate( - 'xpack.inventory.entitiesGrid.euiDataGrid.lastSeenLabel', - { - defaultMessage: 'Last seen', - } -); - -const columns: EuiDataGridColumn[] = [ - { - id: ENTITY_DISPLAY_NAME, - // keep it for accessibility purposes - displayAsText: entityNameLabel, - display: ( - - ), - isSortable: true, - }, - { - id: ENTITY_TYPE, - // keep it for accessibility purposes - displayAsText: entityTypeLabel, - display: ( - - ), - isSortable: true, - }, - { - id: ENTITY_LAST_SEEN, - // keep it for accessibility purposes - displayAsText: entityLastSeenLabel, - display: ( - - ), - defaultSortDirection: 'desc', - isSortable: true, - schema: 'datetime', - }, -]; - interface Props { loading: boolean; entities: LatestEntities; @@ -125,8 +54,6 @@ export function EntitiesGrid({ onChangeSort, onFilterByType, }: Props) { - const [visibleColumns, setVisibleColumns] = useState(columns.map(({ id }) => id)); - const onSort: EuiDataGridSorting['onSort'] = useCallback( (newSortingColumns) => { const lastItem = last(newSortingColumns); @@ -137,6 +64,19 @@ export function EntitiesGrid({ [onChangeSort] ); + const showAlertsColumn = useMemo( + () => entities?.some((entity) => entity?.alertsCount && entity?.alertsCount > 0), + [entities] + ); + + const columnVisibility = useMemo( + () => ({ + visibleColumns: getColumns({ showAlertsColumn }).map(({ id }) => id), + setVisibleColumns: () => {}, + }), + [showAlertsColumn] + ); + const renderCellValue = useCallback( ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { const entity = entities[rowIndex]; @@ -146,6 +86,9 @@ export function EntitiesGrid({ const columnEntityTableId = columnId as EntityColumnIds; switch (columnEntityTableId) { + case 'alertsCount': + return entity?.alertsCount ? : null; + case ENTITY_TYPE: const entityType = entity[columnEntityTableId]; return ( @@ -203,8 +146,8 @@ export function EntitiesGrid({ 'xpack.inventory.entitiesGrid.euiDataGrid.inventoryEntitiesGridLabel', { defaultMessage: 'Inventory entities grid' } )} - columns={columns} - columnVisibility={{ visibleColumns, setVisibleColumns }} + columns={getColumns({ showAlertsColumn })} + columnVisibility={columnVisibility} rowCount={entities.length} renderCellValue={renderCellValue} gridStyle={{ border: 'horizontal', header: 'shade' }} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts index 10ba7fbe4119e..bf72d5d7832cf 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts @@ -15,24 +15,29 @@ export const entitiesMock = [ 'entity.type': 'host', 'entity.displayName': 'Spider-Man', 'entity.id': '0', + alertsCount: 3, }, { 'entity.lastSeenTimestamp': '2024-06-16T21:48:16.259Z', 'entity.type': 'service', 'entity.displayName': 'Iron Man', 'entity.id': '1', + alertsCount: 3, }, + { 'entity.lastSeenTimestamp': '2024-04-28T03:31:57.528Z', 'entity.type': 'host', 'entity.displayName': 'Captain America', 'entity.id': '2', + alertsCount: 10, }, { 'entity.lastSeenTimestamp': '2024-05-14T11:32:04.275Z', 'entity.type': 'host', 'entity.displayName': 'Hulk', 'entity.id': '3', + alertsCount: 1, }, { 'entity.lastSeenTimestamp': '2023-12-05T13:33:54.028Z', @@ -1630,6 +1635,7 @@ export const entitiesMock = [ 'entity.displayName': 'Sed dignissim libero a diam sagittis, in convallis leo pellentesque. Cras ut sapien sed lacus scelerisque vehicula. Pellentesque at purus pulvinar, mollis justo hendrerit, pharetra purus. Morbi dapibus, augue et volutpat ultricies, neque quam sollicitudin mauris, vitae luctus ex libero id erat. Suspendisse risus lectus, scelerisque vel odio sed.', 'entity.id': '269', + alertsCount: 4, }, { 'entity.lastSeenTimestamp': '2023-10-22T13:49:53.092Z', diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx index 90b6213da84a4..ee3014e990b0b 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx @@ -17,10 +17,9 @@ import { ENTITY_LAST_SEEN, ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; -import { defaultEntityDefinitions } from '../../../common/entities'; +import { defaultEntityDefinitions, EntityColumnIds } from '../../../common/entities'; import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useKibana } from '../../hooks/use_kibana'; -import { EntityColumnIds } from '../entities_grid'; const ACTIVE_COLUMNS: EntityColumnIds[] = [ENTITY_DISPLAY_NAME, ENTITY_TYPE, ENTITY_LAST_SEEN]; diff --git a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx index 7af9a9fc21acc..965434eeac6d1 100644 --- a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx @@ -7,7 +7,7 @@ import { EuiDataGridSorting } from '@elastic/eui'; import React from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { EntityType } from '../../../common/entities'; +import { EntityColumnIds, EntityType } from '../../../common/entities'; import { EntitiesGrid } from '../../components/entities_grid'; import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; @@ -76,7 +76,7 @@ export function InventoryPage() { path: {}, query: { ...query, - sortField: sorting.id, + sortField: sorting.id as EntityColumnIds, sortDirection: sorting.direction, }, }); diff --git a/x-pack/plugins/observability_solution/inventory/public/plugin.ts b/x-pack/plugins/observability_solution/inventory/public/plugin.ts index 4567e8f34a94a..b6771d2f95550 100644 --- a/x-pack/plugins/observability_solution/inventory/public/plugin.ts +++ b/x-pack/plugins/observability_solution/inventory/public/plugin.ts @@ -117,7 +117,7 @@ export class InventoryPlugin defaultMessage: 'Inventory', }), euiIconType: 'logoObservability', - appRoute: '/app/observability/inventory', + appRoute: '/app/inventory', category: DEFAULT_APP_CATEGORIES.observability, visibleIn: ['sideNav', 'globalSearch'], order: 8200, diff --git a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx index d67a7250f75a5..dc7ba13451e02 100644 --- a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx @@ -8,10 +8,9 @@ import { toNumberRt } from '@kbn/io-ts-utils'; import { Outlet, createRouter } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; -import { ENTITY_LAST_SEEN } from '@kbn/observability-shared-plugin/common'; import { InventoryPageTemplate } from '../components/inventory_page_template'; import { InventoryPage } from '../pages/inventory_page'; -import { entityTypesRt } from '../../common/entities'; +import { defaultEntitySortField, entityTypesRt, entityColumnIdsRt } from '../../common/entities'; /** * The array of route definitions to be used when the application @@ -27,7 +26,7 @@ const inventoryRoutes = { params: t.type({ query: t.intersection([ t.type({ - sortField: t.string, + sortField: entityColumnIdsRt, sortDirection: t.union([t.literal('asc'), t.literal('desc')]), pageIndex: toNumberRt, }), @@ -39,7 +38,7 @@ const inventoryRoutes = { }), defaults: { query: { - sortField: ENTITY_LAST_SEEN, + sortField: defaultEntitySortField, sortDirection: 'desc', pageIndex: '0', }, diff --git a/x-pack/plugins/observability_solution/inventory/server/lib/create_alerts_client.ts/create_alerts_client.ts b/x-pack/plugins/observability_solution/inventory/server/lib/create_alerts_client.ts/create_alerts_client.ts new file mode 100644 index 0000000000000..150e946fd98d6 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/lib/create_alerts_client.ts/create_alerts_client.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash'; +import { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import { InventoryRouteHandlerResources } from '../../routes/types'; + +export type AlertsClient = Awaited>; + +export async function createAlertsClient({ + plugins, + request, +}: Pick) { + const ruleRegistryPluginStart = await plugins.ruleRegistry.start(); + const alertsClient = await ruleRegistryPluginStart.getRacClientWithRequest(request); + const alertsIndices = await alertsClient.getAuthorizedAlertsIndices([ + 'logs', + 'infrastructure', + 'apm', + 'slo', + 'observability', + ]); + + if (!alertsIndices || isEmpty(alertsIndices)) { + throw Error('No alert indices exist'); + } + type RequiredParams = ESSearchRequest & { + size: number; + track_total_hits: boolean | number; + }; + + return { + search( + searchParams: TParams + ): Promise> { + return alertsClient.find({ + ...searchParams, + index: alertsIndices.join(','), + }) as Promise; + }, + }; +} diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.test.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.test.ts new file mode 100644 index 0000000000000..03027430116e6 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { getGroupByTermsAgg } from './get_group_by_terms_agg'; +import { IdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; + +describe('getGroupByTermsAgg', () => { + it('should return an empty object when fields is empty', () => { + const fields: IdentityFieldsPerEntityType = new Map(); + const result = getGroupByTermsAgg(fields); + expect(result).toEqual({}); + }); + + it('should correctly generate aggregation structure for service, host, and container entity types', () => { + const fields: IdentityFieldsPerEntityType = new Map([ + ['service', ['service.name', 'service.environment']], + ['host', ['host.name']], + ['container', ['container.id', 'foo.bar']], + ]); + + const result = getGroupByTermsAgg(fields); + + expect(result).toEqual({ + service: { + composite: { + size: 500, + sources: [ + { 'service.name': { terms: { field: 'service.name' } } }, + { 'service.environment': { terms: { field: 'service.environment' } } }, + ], + }, + }, + host: { + composite: { + size: 500, + sources: [{ 'host.name': { terms: { field: 'host.name' } } }], + }, + }, + container: { + composite: { + size: 500, + sources: [ + { + 'container.id': { + terms: { field: 'container.id' }, + }, + }, + { + 'foo.bar': { terms: { field: 'foo.bar' } }, + }, + ], + }, + }, + }); + }); + it('should override maxSize when provided', () => { + const fields: IdentityFieldsPerEntityType = new Map([['host', ['host.name']]]); + const result = getGroupByTermsAgg(fields, 10); + expect(result.host.composite.size).toBe(10); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.ts new file mode 100644 index 0000000000000..96ab3eb24444a --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_group_by_terms_agg.ts @@ -0,0 +1,26 @@ +/* + * 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 { IdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; + +export const getGroupByTermsAgg = (fields: IdentityFieldsPerEntityType, maxSize = 500) => { + return Array.from(fields).reduce((acc, [entityType, identityFields]) => { + acc[entityType] = { + composite: { + size: maxSize, + sources: identityFields.map((field) => ({ + [field]: { + terms: { + field, + }, + }, + })), + }, + }; + return acc; + }, {} as Record); +}; diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts new file mode 100644 index 0000000000000..90bf2967b894d --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts @@ -0,0 +1,64 @@ +/* + * 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 { ContainerEntity, HostEntity, ServiceEntity } from '../../../common/entities'; +import { + ENTITY_DEFINITION_ID, + ENTITY_DISPLAY_NAME, + ENTITY_ID, + ENTITY_LAST_SEEN, +} from '@kbn/observability-shared-plugin/common'; +import { getIdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; + +const commonEntityFields = { + [ENTITY_LAST_SEEN]: '2023-10-09T00:00:00Z', + [ENTITY_ID]: '1', + [ENTITY_DISPLAY_NAME]: 'entity_name', + [ENTITY_DEFINITION_ID]: 'entity_definition_id', + alertCount: 3, +}; +describe('getIdentityFields', () => { + it('should return an empty Map when no entities are provided', () => { + const result = getIdentityFieldsPerEntityType([]); + expect(result.size).toBe(0); + }); + it('should return a Map with unique entity types and their respective identity fields', () => { + const serviceEntity: ServiceEntity = { + 'agent.name': 'node', + 'entity.identityFields': ['service.name', 'service.environment'], + 'service.name': 'my-service', + 'entity.type': 'service', + ...commonEntityFields, + }; + + const hostEntity: HostEntity = { + 'entity.identityFields': ['host.name'], + 'host.name': 'my-host', + 'entity.type': 'host', + 'cloud.provider': null, + ...commonEntityFields, + }; + + const containerEntity: ContainerEntity = { + 'entity.identityFields': 'container.id', + 'host.name': 'my-host', + 'entity.type': 'container', + 'cloud.provider': null, + 'container.id': '123', + ...commonEntityFields, + }; + + const mockEntities = [serviceEntity, hostEntity, containerEntity]; + const result = getIdentityFieldsPerEntityType(mockEntities); + + expect(result.size).toBe(3); + + expect(result.get('service')).toEqual(['service.name', 'service.environment']); + expect(result.get('host')).toEqual(['host.name']); + expect(result.get('container')).toEqual(['container.id']); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts new file mode 100644 index 0000000000000..0ca4eb9d21239 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ENTITY_IDENTITY_FIELDS, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; +import { Entity, EntityType } from '../../../common/entities'; + +export type IdentityFieldsPerEntityType = Map; + +export const getIdentityFieldsPerEntityType = (entities: Entity[]) => { + const identityFieldsPerEntityType: IdentityFieldsPerEntityType = new Map(); + + entities.forEach((entity) => + identityFieldsPerEntityType.set(entity[ENTITY_TYPE], [entity[ENTITY_IDENTITY_FIELDS]].flat()) + ); + + return identityFieldsPerEntityType; +}; diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts index 853d52d8401a9..e500ce32c3cef 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts @@ -8,11 +8,13 @@ import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; import { kqlQuery } from '@kbn/observability-utils/es/queries/kql_query'; import { esqlResultToPlainObjects } from '@kbn/observability-utils/es/utils/esql_result_to_plain_objects'; +import { ENTITY_LAST_SEEN } from '@kbn/observability-shared-plugin/common'; import { ENTITIES_LATEST_ALIAS, MAX_NUMBER_OF_ENTITIES, type EntityType, - Entity, + type Entity, + type EntityColumnIds, } from '../../../common/entities'; import { getEntityDefinitionIdWhereClause, getEntityTypesWhereClause } from './query_helper'; @@ -25,15 +27,18 @@ export async function getLatestEntities({ }: { inventoryEsClient: ObservabilityElasticsearchClient; sortDirection: 'asc' | 'desc'; - sortField: string; + sortField: EntityColumnIds; entityTypes?: EntityType[]; kuery?: string; }) { - const latestEntitiesEsqlResponse = await inventoryEsClient.esql('get_latest_entities', { + // alertsCount doesn't exist in entities index. Ignore it and sort by entity.lastSeenTimestamp by default. + const entitiesSortField = sortField === 'alertsCount' ? ENTITY_LAST_SEEN : sortField; + + const request = { query: `FROM ${ENTITIES_LATEST_ALIAS} | ${getEntityTypesWhereClause(entityTypes)} | ${getEntityDefinitionIdWhereClause()} - | SORT ${sortField} ${sortDirection} + | SORT ${entitiesSortField} ${sortDirection} | LIMIT ${MAX_NUMBER_OF_ENTITIES} `, filter: { @@ -41,7 +46,9 @@ export async function getLatestEntities({ filter: [...kqlQuery(kuery)], }, }, - }); + }; + + const latestEntitiesEsqlResponse = await inventoryEsClient.esql('get_latest_entities', request); return esqlResultToPlainObjects(latestEntitiesEsqlResponse); } diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts new file mode 100644 index 0000000000000..4e6ce545a079e --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts @@ -0,0 +1,65 @@ +/* + * 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 { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; +import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; +import { AlertsClient } from '../../lib/create_alerts_client.ts/create_alerts_client'; +import { getGroupByTermsAgg } from './get_group_by_terms_agg'; +import { IdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; +import { EntityType } from '../../../common/entities'; + +interface Bucket { + key: Record; + doc_count: number; +} + +type EntityTypeBucketsAggregation = Record; + +export async function getLatestEntitiesAlerts({ + alertsClient, + kuery, + identityFieldsPerEntityType, +}: { + alertsClient: AlertsClient; + kuery?: string; + identityFieldsPerEntityType: IdentityFieldsPerEntityType; +}): Promise> { + if (identityFieldsPerEntityType.size === 0) { + return []; + } + + const filter = { + size: 0, + track_total_hits: false, + query: { + bool: { + filter: [...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE), ...kqlQuery(kuery)], + }, + }, + }; + + const response = await alertsClient.search({ + ...filter, + aggs: getGroupByTermsAgg(identityFieldsPerEntityType), + }); + + const aggregations = response.aggregations as EntityTypeBucketsAggregation; + + const alerts = Array.from(identityFieldsPerEntityType).flatMap(([entityType]) => { + const entityAggregation = aggregations?.[entityType]; + + const buckets = entityAggregation.buckets ?? []; + + return buckets.map((bucket: Bucket) => ({ + alertsCount: bucket.doc_count, + type: entityType, + ...bucket.key, + })); + }); + + return alerts; +} diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts index beef1b068ed15..eb80f80d02730 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts @@ -8,10 +8,15 @@ import { INVENTORY_APP_ID } from '@kbn/deeplinks-observability/constants'; import { jsonRt } from '@kbn/io-ts-utils'; import { createObservabilityEsClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; import * as t from 'io-ts'; -import { entityTypeRt } from '../../../common/entities'; +import { orderBy } from 'lodash'; +import { joinByKey } from '@kbn/observability-utils/array/join_by_key'; +import { entityTypeRt, entityColumnIdsRt, Entity } from '../../../common/entities'; import { createInventoryServerRoute } from '../create_inventory_server_route'; import { getEntityTypes } from './get_entity_types'; import { getLatestEntities } from './get_latest_entities'; +import { createAlertsClient } from '../../lib/create_alerts_client.ts/create_alerts_client'; +import { getLatestEntitiesAlerts } from './get_latest_entities_alerts'; +import { getIdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; export const getEntityTypesRoute = createInventoryServerRoute({ endpoint: 'GET /internal/inventory/entities/types', @@ -36,7 +41,7 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ params: t.type({ query: t.intersection([ t.type({ - sortField: t.string, + sortField: entityColumnIdsRt, sortDirection: t.union([t.literal('asc'), t.literal('desc')]), }), t.partial({ @@ -48,7 +53,7 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ options: { tags: ['access:inventory'], }, - handler: async ({ params, context, logger }) => { + handler: async ({ params, context, logger, plugins, request }) => { const coreContext = await context.core; const inventoryEsClient = createObservabilityEsClient({ client: coreContext.elasticsearch.client.asCurrentUser, @@ -58,15 +63,40 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ const { sortDirection, sortField, entityTypes, kuery } = params.query; - const latestEntities = await getLatestEntities({ - inventoryEsClient, - sortDirection, - sortField, - entityTypes, + const [alertsClient, latestEntities] = await Promise.all([ + createAlertsClient({ plugins, request }), + getLatestEntities({ + inventoryEsClient, + sortDirection, + sortField, + entityTypes, + kuery, + }), + ]); + + const identityFieldsPerEntityType = getIdentityFieldsPerEntityType(latestEntities); + + const alerts = await getLatestEntitiesAlerts({ + identityFieldsPerEntityType, + alertsClient, kuery, }); - return { entities: latestEntities }; + const joined = joinByKey( + [...latestEntities, ...alerts], + [...identityFieldsPerEntityType.values()].flat() + ).filter((entity) => entity['entity.id']); + + return { + entities: + sortField === 'alertsCount' + ? orderBy( + joined, + [(item: Entity) => item?.alertsCount === undefined, sortField], + ['asc', sortDirection] // push entities without alertsCount to the end + ) + : joined, + }; }, }); diff --git a/x-pack/plugins/observability_solution/inventory/server/types.ts b/x-pack/plugins/observability_solution/inventory/server/types.ts index 05f75561674c6..d3d5ef0fb7f60 100644 --- a/x-pack/plugins/observability_solution/inventory/server/types.ts +++ b/x-pack/plugins/observability_solution/inventory/server/types.ts @@ -14,6 +14,10 @@ import type { DataViewsServerPluginStart, } from '@kbn/data-views-plugin/server'; import { FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import { + RuleRegistryPluginStartContract, + RuleRegistryPluginSetupContract, +} from '@kbn/rule-registry-plugin/server'; /* eslint-disable @typescript-eslint/no-empty-interface*/ export interface ConfigSchema {} @@ -23,12 +27,14 @@ export interface InventorySetupDependencies { inference: InferenceServerSetup; dataViews: DataViewsServerPluginSetup; features: FeaturesPluginSetup; + ruleRegistry: RuleRegistryPluginSetupContract; } export interface InventoryStartDependencies { entityManager: EntityManagerServerPluginStart; inference: InferenceServerStart; dataViews: DataViewsServerPluginStart; + ruleRegistry: RuleRegistryPluginStartContract; } export interface InventoryServerSetup {} diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index 6492cd51d067a..c4c8f5d3ac59d 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -46,6 +46,10 @@ "@kbn/elastic-agent-utils", "@kbn/custom-icons", "@kbn/ui-theme", + "@kbn/rison", + "@kbn/rule-registry-plugin", + "@kbn/observability-plugin", + "@kbn/rule-data-utils", "@kbn/spaces-plugin", "@kbn/cloud-plugin" ] From 1055120d0f4640af67881b4909d4881681d9575d Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 15 Oct 2024 13:55:53 +0200 Subject: [PATCH 17/84] fix `no-restricted-imports` (#195456) ## Summary I noticed that our `no-restricted-imports` rules were not working on some parts of the codebase. Turns our the rule was overriden by mistake. This PR fixes the rules and places that were not following them: - lodash set for safety - react-use for a bit smaller bundles - router for context annoncement (`useExecutionContext`) and hopefully easier upgrade to newer version --- .eslintrc.js | 13 ++++++------- .github/CODEOWNERS | 2 ++ .../impl/assistant/index.test.tsx | 6 ++++-- .../quick_prompts/quick_prompts.test.tsx | 15 ++++++--------- .../assistant/quick_prompts/quick_prompts.tsx | 2 +- .../impl/assistant_context/index.test.tsx | 8 +++----- .../impl/assistant_context/index.tsx | 3 ++- .../cases/common/types/domain/user/v1.test.ts | 2 +- .../visualizations/open_lens_button.test.tsx | 2 +- .../connectors/cases/cases_oracle_service.test.ts | 3 ++- .../server/connectors/cases/cases_service.test.ts | 3 ++- .../server/services/user_actions/index.test.ts | 3 ++- .../user_actions/operations/create.test.ts | 3 ++- .../public/common/hooks/use_availability.ts | 2 +- .../create_integration/create_integration.tsx | 8 ++++---- .../common/alerting_callout/alerting_callout.tsx | 2 +- .../monitor_add_edit/form/controlled_field.tsx | 2 +- .../monitor_status/use_monitor_status_data.ts | 2 +- .../monitors_page/hooks/use_monitor_list.ts | 2 +- .../overview/grid_by_group/grid_group_item.tsx | 2 +- .../settings/global_params/params_list.tsx | 2 +- .../contexts/synthetics_refresh_context.tsx | 2 +- .../synthetics/hooks/use_breadcrumbs.test.tsx | 2 +- .../apps/synthetics/hooks/use_monitor_name.ts | 2 +- .../synthetics/utils/formatting/test_helpers.ts | 1 + .../endpoint_metadata_generator.ts | 3 ++- .../endpoint/models/policy_config_helpers.test.ts | 3 ++- .../endpoint/models/policy_config_helpers.ts | 3 ++- .../common/utils/expand_dotted.ts | 3 ++- .../cell_action/add_to_timeline.test.ts | 2 +- .../top_values_popover.test.tsx | 5 +---- .../top_values_popover/top_values_popover.tsx | 2 +- .../public/assistant/provider.tsx | 2 +- .../public/attack_discovery/pages/index.test.tsx | 15 +++++---------- .../public/attack_discovery/pages/index.tsx | 2 +- .../use_security_solution_navigation.tsx | 2 +- .../components/visualization_actions/actions.tsx | 2 +- .../lib/endpoint/utils/get_host_platform.test.ts | 2 +- .../public/common/mock/router.tsx | 1 + .../utils/global_query_string/helpers.test.tsx | 1 + .../related_integrations_help_info.tsx | 2 +- .../required_fields/required_fields_help_info.tsx | 2 +- .../comparison_side/comparison_side_help_info.tsx | 2 +- .../final_edit/fields/kql_query.tsx | 2 +- .../final_side/final_side_help_info.tsx | 2 +- .../add_prebuilt_rules_header_buttons.tsx | 2 +- .../add_prebuilt_rules_install_button.tsx | 2 +- .../asset_criticality_selector.tsx | 2 +- .../public/entity_analytics/routes.tsx | 11 +++++------ .../explore/network/pages/details/index.test.tsx | 3 ++- .../privileged_route/privileged_route.test.tsx | 1 + .../policy/use_fetch_endpoint_policy.test.ts | 2 +- .../components/advanced_section.test.tsx | 2 +- .../cards/attack_surface_reduction_card.test.tsx | 3 ++- .../cards/behaviour_protection_card.test.tsx | 2 +- .../cards/linux_event_collection_card.test.tsx | 2 +- .../cards/mac_event_collection_card.test.tsx | 2 +- .../cards/malware_protections_card.test.tsx | 3 ++- .../cards/memory_protection_card.test.tsx | 2 +- .../cards/ransomware_protection_card.test.tsx | 2 +- .../cards/windows_event_collection_card.test.tsx | 2 +- .../detect_prevent_protection_level.test.tsx | 3 ++- .../components/event_collection_card.test.tsx | 3 ++- .../components/event_collection_card.tsx | 3 ++- .../components/notify_user_option.test.tsx | 3 ++- .../protection_setting_card_switch.test.tsx | 3 ++- .../policy/view/policy_settings_form/mocks.ts | 2 +- .../policy_settings_layout.test.tsx | 3 ++- .../security_solution/public/notes/routes.tsx | 7 +++---- .../callouts/integration_card_top_callout.tsx | 2 +- .../onboarding_body/hooks/use_body_config.test.ts | 4 ++-- .../onboarding_body/hooks/use_body_config.ts | 2 +- .../cards/teammates_card/teammates_card.tsx | 2 +- .../public/onboarding/hooks/use_stored_state.ts | 2 +- .../timelines/store/middlewares/timeline_save.ts | 3 ++- .../routes/actions/response_actions.test.ts | 3 ++- .../lib/base_response_actions_client.test.ts | 2 +- .../factories/utils/traverse_and_mutate_doc.ts | 3 ++- .../server/lib/telemetry/helpers.ts | 3 ++- 79 files changed, 131 insertions(+), 117 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e46dde5a3c56f..006f39ce1026c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1014,6 +1014,7 @@ module.exports = { 'error', { patterns: ['**/legacy_uptime/*'], + paths: RESTRICTED_IMPORTS, }, ], }, @@ -1055,6 +1056,7 @@ module.exports = { { // prevents UI code from importing server side code and then webpack including it when doing builds patterns: ['**/server/*'], + paths: RESTRICTED_IMPORTS, }, ], }, @@ -1113,6 +1115,7 @@ module.exports = { { // prevents UI code from importing server side code and then webpack including it when doing builds patterns: ['**/server/*'], + paths: RESTRICTED_IMPORTS, }, ], }, @@ -1184,13 +1187,7 @@ module.exports = { // to help deprecation and prevent accidental re-use/continued use of code we plan on removing. If you are // finding yourself turning this off a lot for "new code" consider renaming the file and functions if it is has valid uses. patterns: ['*legacy*'], - paths: [ - { - name: 'react-router-dom', - importNames: ['Route'], - message: "import { Route } from '@kbn/kibana-react-plugin/public'", - }, - ], + paths: RESTRICTED_IMPORTS, }, ], }, @@ -1348,6 +1345,7 @@ module.exports = { { // prevents UI code from importing server side code and then webpack including it when doing builds patterns: ['**/server/*'], + paths: RESTRICTED_IMPORTS, }, ], }, @@ -1525,6 +1523,7 @@ module.exports = { // to help deprecation and prevent accidental re-use/continued use of code we plan on removing. If you are // finding yourself turning this off a lot for "new code" consider renaming the file and functions if it has valid uses. patterns: ['*legacy*'], + paths: RESTRICTED_IMPORTS, }, ], }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bd0fa1bc13104..7c7634aab7231 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1327,6 +1327,8 @@ x-pack/test_serverless/**/test_suites/observability/ai_assistant @elastic/obs-ai /x-pack/dev-tools @elastic/kibana-operations /catalog-info.yaml @elastic/kibana-operations @elastic/kibana-tech-leads /.devcontainer/ @elastic/kibana-operations +/.eslintrc.js @elastic/kibana-operations +/.eslintignore @elastic/kibana-operations # Appex QA /x-pack/test_serverless/tsconfig.json @elastic/appex-qa diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx index 4b1851834cdba..d042a4cfd96f5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -15,7 +15,8 @@ import { useLoadConnectors } from '../connectorland/use_load_connectors'; import { DefinedUseQueryResult, UseQueryResult } from '@tanstack/react-query'; -import { useLocalStorage, useSessionStorage } from 'react-use'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import useSessionStorage from 'react-use/lib/useSessionStorage'; import { QuickPrompts } from './quick_prompts/quick_prompts'; import { mockAssistantAvailability, TestProviders } from '../mock/test_providers/test_providers'; import { useFetchCurrentUserConversations } from './api'; @@ -27,7 +28,8 @@ import { omit } from 'lodash'; jest.mock('../connectorland/use_load_connectors'); jest.mock('../connectorland/connector_setup'); -jest.mock('react-use'); +jest.mock('react-use/lib/useLocalStorage'); +jest.mock('react-use/lib/useSessionStorage'); jest.mock('./quick_prompts/quick_prompts', () => ({ QuickPrompts: jest.fn() })); jest.mock('./api/conversations/use_fetch_current_user_conversations'); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx index e46f54ddede40..c3927a939af92 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx @@ -32,15 +32,12 @@ const testTitle = 'SPL_QUERY_CONVERSION_TITLE'; const testPrompt = 'SPL_QUERY_CONVERSION_PROMPT'; const customTitle = 'A_CUSTOM_OPTION'; -jest.mock('react-use', () => ({ - ...jest.requireActual('react-use'), - useMeasure: () => [ - () => {}, - { - width: 500, - }, - ], -})); +jest.mock('react-use/lib/useMeasure', () => () => [ + () => {}, + { + width: 500, + }, +]); jest.mock('../../assistant_context', () => ({ ...jest.requireActual('../../assistant_context'), diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx index 036fb4fb4db3f..f2baf4528b52d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx @@ -14,7 +14,7 @@ import { EuiButtonIcon, EuiButtonEmpty, } from '@elastic/eui'; -import { useMeasure } from 'react-use'; +import useMeasure from 'react-use/lib/useMeasure'; import { css } from '@emotion/react'; import { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx index 5bd49fec6c857..4e877e1886fb4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx @@ -8,13 +8,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { useAssistantContext } from '.'; -import { useLocalStorage } from 'react-use'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { TestProviders } from '../mock/test_providers/test_providers'; -jest.mock('react-use', () => ({ - useLocalStorage: jest.fn().mockReturnValue(['456', jest.fn()]), - useSessionStorage: jest.fn().mockReturnValue(['456', jest.fn()]), -})); +jest.mock('react-use/lib/useLocalStorage', () => jest.fn().mockReturnValue(['456', jest.fn()])); +jest.mock('react-use/lib/useSessionStorage', () => jest.fn().mockReturnValue(['456', jest.fn()])); describe('AssistantContext', () => { beforeEach(() => jest.clearAllMocks()); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index 75516eaf907b2..c7b15f681a717 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -10,7 +10,8 @@ import { omit } from 'lodash/fp'; import React, { useCallback, useMemo, useState, useRef } from 'react'; import type { IToasts } from '@kbn/core-notifications-browser'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { useLocalStorage, useSessionStorage } from 'react-use'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import useSessionStorage from 'react-use/lib/useSessionStorage'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { AssistantFeatures, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; import { NavigateToAppOptions } from '@kbn/core/public'; diff --git a/x-pack/plugins/cases/common/types/domain/user/v1.test.ts b/x-pack/plugins/cases/common/types/domain/user/v1.test.ts index 56d23fff6fc1a..3c90054857e93 100644 --- a/x-pack/plugins/cases/common/types/domain/user/v1.test.ts +++ b/x-pack/plugins/cases/common/types/domain/user/v1.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { UserRt, UserWithProfileInfoRt, UsersRt, CaseUserProfileRt, CaseAssigneesRt } from './v1'; describe('User', () => { diff --git a/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx b/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx index 7ac2ed8d45da4..752bdd2980987 100644 --- a/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import React from 'react'; import { screen } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/server/connectors/cases/cases_oracle_service.test.ts b/x-pack/plugins/cases/server/connectors/cases/cases_oracle_service.test.ts index ea64b20f2c1a2..4d5d167a58852 100644 --- a/x-pack/plugins/cases/server/connectors/cases/cases_oracle_service.test.ts +++ b/x-pack/plugins/cases/server/connectors/cases/cases_oracle_service.test.ts @@ -12,7 +12,8 @@ import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { CasesOracleService } from './cases_oracle_service'; import { CASE_RULES_SAVED_OBJECT } from '../../../common/constants'; -import { isEmpty, set } from 'lodash'; +import { isEmpty } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; describe('CasesOracleService', () => { const savedObjectsClient = savedObjectsClientMock.create(); diff --git a/x-pack/plugins/cases/server/connectors/cases/cases_service.test.ts b/x-pack/plugins/cases/server/connectors/cases/cases_service.test.ts index 848d3fa276236..183d628d7a742 100644 --- a/x-pack/plugins/cases/server/connectors/cases/cases_service.test.ts +++ b/x-pack/plugins/cases/server/connectors/cases/cases_service.test.ts @@ -8,7 +8,8 @@ import { createHash } from 'node:crypto'; import stringify from 'json-stable-stringify'; -import { isEmpty, set } from 'lodash'; +import { isEmpty } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { CasesService } from './cases_service'; describe('CasesService', () => { diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts index 20c06f2701fed..9e5b7589f1626 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { set, omit, unset } from 'lodash'; +import { omit, unset } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { loggerMock } from '@kbn/logging-mocks'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { diff --git a/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts b/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts index 833e8676a2619..38fb3e4e746ec 100644 --- a/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts @@ -11,7 +11,8 @@ import { createSavedObjectsSerializerMock } from '../../../client/mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; -import { set, unset } from 'lodash'; +import { unset } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { createConnectorObject } from '../../test_utils'; import { UserActionPersister } from './create'; import { createUserActionSO } from '../test_utils'; diff --git a/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts b/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts index 02f523fcde226..3fdf37297ad65 100644 --- a/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts +++ b/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { MINIMUM_LICENSE_TYPE } from '../../../common/constants'; import { useKibana } from './use_kibana'; import type { RenderUpselling } from '../../services'; diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx index 494bc94d8c58c..6afacc8e417f3 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx @@ -5,8 +5,8 @@ * 2.0. */ import React from 'react'; -import { Redirect, Switch } from 'react-router-dom'; -import { Route } from '@kbn/shared-ux-router'; +import { Redirect } from 'react-router-dom'; +import { Route, Routes } from '@kbn/shared-ux-router'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { Services } from '../../services'; import { TelemetryContextProvider } from './telemetry'; @@ -33,7 +33,7 @@ const CreateIntegrationRouter = React.memo(() => { const { canUseIntegrationAssistant, canUseIntegrationUpload } = useRoutesAuthorization(); const isAvailable = useIsAvailable(); return ( - + {isAvailable && canUseIntegrationAssistant && ( )} @@ -44,7 +44,7 @@ const CreateIntegrationRouter = React.memo(() => { } /> - + ); }); CreateIntegrationRouter.displayName = 'CreateIntegrationRouter'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx index 397b1597107c4..a6353c674d7c0 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx @@ -11,7 +11,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { EuiButton, EuiButtonEmpty, EuiCallOut, EuiMarkdownFormat, EuiSpacer } from '@elastic/eui'; import { syntheticsSettingsLocatorID } from '@kbn/observability-plugin/common'; import { useFetcher } from '@kbn/observability-shared-plugin/public'; -import { useSessionStorage } from 'react-use'; +import useSessionStorage from 'react-use/lib/useSessionStorage'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import { useKibana } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx index cc37a530087c4..ddf1db76d819f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useState } from 'react'; import { EuiFormRow, EuiFormRowProps } from '@elastic/eui'; import { useSelector } from 'react-redux'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { ControllerRenderProps, ControllerFieldState, useFormContext } from 'react-hook-form'; import { useKibanaSpace, useIsEditFlow } from '../hooks'; import { selectServiceLocationsState } from '../../../state'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts index 8eaa80fb44a53..710ff65de7c66 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts @@ -7,7 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { useLocation } from 'react-router-dom'; import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts index 29e1f550d43cf..df8be3c98b451 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts @@ -7,7 +7,7 @@ import { useCallback, useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { useMonitorFiltersState } from '../common/monitor_filters/use_filters'; import { fetchMonitorListAction, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/grid_by_group/grid_group_item.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/grid_by_group/grid_group_item.tsx index f9f0a417e065e..6fcf90f631fad 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/grid_by_group/grid_group_item.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/grid_by_group/grid_group_item.tsx @@ -19,7 +19,7 @@ import { import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useSelector } from 'react-redux'; -import { useKey } from 'react-use'; +import useKey from 'react-use/lib/useKey'; import { FlyoutParamProps } from '../types'; import { OverviewLoader } from '../overview_loader'; import { useFilteredGroupMonitors } from './use_filtered_group_monitors'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx index d72d92156e42e..2ff3ea547ae9f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { TableTitle } from '../../common/components/table_title'; import { ParamsText } from './params_text'; import { SyntheticsParams } from '../../../../../../common/runtime_types'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx index 9f3902b8ccaf2..68f6910b43b78 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx @@ -15,7 +15,7 @@ import React, { FC, } from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { useEvent } from 'react-use'; +import useEvent from 'react-use/lib/useEvent'; import moment from 'moment'; import { Subject } from 'rxjs'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.test.tsx index 6a07150070362..5e524eca31bda 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.test.tsx @@ -9,7 +9,7 @@ import { ChromeBreadcrumb } from '@kbn/core/public'; import { render } from '../utils/testing'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { Route } from 'react-router-dom'; +import { Route } from '@kbn/shared-ux-router'; import { OVERVIEW_ROUTE } from '../../../../common/constants'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_name.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_name.ts index 717399d94d1fc..b90044725d070 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_name.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_name.ts @@ -7,7 +7,7 @@ import { useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { useFetcher } from '@kbn/observability-shared-plugin/public'; import { fetchMonitorManagementList, getMonitorListPageStateWithDefaults } from '../state'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/formatting/test_helpers.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/formatting/test_helpers.ts index 0b32c4a2420e8..8ba26624f3f0c 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/formatting/test_helpers.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/formatting/test_helpers.ts @@ -8,6 +8,7 @@ import moment from 'moment'; import { Moment } from 'moment-timezone'; import * as redux from 'react-redux'; +// eslint-disable-next-line no-restricted-imports import * as reactRouterDom from 'react-router-dom'; export function mockMoment() { diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts index 558a9b8371068..b14ddc1e8af9e 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts @@ -8,7 +8,8 @@ /* eslint-disable max-classes-per-file */ import type { DeepPartial } from 'utility-types'; -import { merge, set } from 'lodash'; +import { merge } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { gte } from 'semver'; import type { EndpointCapabilities } from '../service/response_actions/constants'; import { BaseDataGenerator } from './base_data_generator'; diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts index 5d7cc61d1d7bd..603ec6b1ac6e3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts @@ -17,7 +17,8 @@ import { checkIfPopupMessagesContainCustomNotifications, resetCustomNotifications, } from './policy_config_helpers'; -import { get, merge, set } from 'lodash'; +import { get, merge } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; describe('Policy Config helpers', () => { describe('disableProtections', () => { diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts index 9b3906191b698..5079493724d78 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { get, set } from 'lodash'; +import { get } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { DefaultPolicyNotificationMessage } from './policy_config'; import type { PolicyConfig } from '../types'; import { PolicyOperatingSystem, ProtectionModes, AntivirusRegistrationModes } from '../types'; diff --git a/x-pack/plugins/security_solution/common/utils/expand_dotted.ts b/x-pack/plugins/security_solution/common/utils/expand_dotted.ts index e919b71dcdcf4..d452ca4df9fb6 100644 --- a/x-pack/plugins/security_solution/common/utils/expand_dotted.ts +++ b/x-pack/plugins/security_solution/common/utils/expand_dotted.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { merge, setWith } from 'lodash'; +import { merge } from 'lodash'; +import { setWith } from '@kbn/safer-lodash-set'; /* * Expands an object with "dotted" fields to a nested object with unflattened fields. diff --git a/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/cell_action/add_to_timeline.test.ts b/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/cell_action/add_to_timeline.test.ts index dfdc2a5ede83f..3d105c34515b4 100644 --- a/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/cell_action/add_to_timeline.test.ts +++ b/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/cell_action/add_to_timeline.test.ts @@ -12,7 +12,7 @@ import { createAddToTimelineCellActionFactory } from './add_to_timeline'; import type { CellActionExecutionContext } from '@kbn/cell-actions'; import { GEO_FIELD_TYPE } from '../../../../timelines/components/timeline/body/renderers/constants'; import { createStartServicesMock } from '../../../../common/lib/kibana/kibana_react.mock'; -import { set } from 'lodash/fp'; +import { set } from '@kbn/safer-lodash-set/fp'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; const services = createStartServicesMock(); diff --git a/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.test.tsx b/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.test.tsx index 80b22c42b544e..ed65f8a12a02a 100644 --- a/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.test.tsx +++ b/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.test.tsx @@ -29,10 +29,7 @@ const data = { const mockUseObservable = jest.fn(); -jest.mock('react-use', () => ({ - ...jest.requireActual('react-use'), - useObservable: () => mockUseObservable(), -})); +jest.mock('react-use/lib/useObservable', () => () => mockUseObservable()); jest.mock('../../../common/lib/kibana', () => { const original = jest.requireActual('../../../common/lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.tsx b/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.tsx index ad88362e9e861..f03be50f39660 100644 --- a/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.tsx +++ b/x-pack/plugins/security_solution/public/app/components/top_values_popover/top_values_popover.tsx @@ -8,7 +8,7 @@ import React, { useCallback } from 'react'; import { EuiWrappingPopover } from '@elastic/eui'; import { useLocation } from 'react-router-dom'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { StatefulTopN } from '../../../common/components/top_n'; import { getScopeFromPath } from '../../../sourcerer/containers/sourcerer_paths'; import { useSourcererDataView } from '../../../sourcerer/containers'; diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 54d4e47edb684..93c65bb463584 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -23,7 +23,7 @@ import { once } from 'lodash/fp'; import type { HttpSetup } from '@kbn/core-http-browser'; import type { Message } from '@kbn/elastic-assistant-common'; import { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugin/public/common/constants'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { APP_ID } from '../../common'; import { useBasePath, useKibana } from '../common/lib/kibana'; import { useAssistantTelemetry } from './use_assistant_telemetry'; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.test.tsx index 97f98b81dc153..8a53cd81db96a 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.test.tsx @@ -13,7 +13,7 @@ import { UpsellingService } from '@kbn/security-solution-upselling/service'; import { Router } from '@kbn/shared-ux-router'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { useLocalStorage } from 'react-use'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { TestProviders } from '../../common/mock'; import { ATTACK_DISCOVERY_PATH } from '../../../common/constants'; @@ -38,15 +38,10 @@ const mockConnectors: unknown[] = [ }, ]; -jest.mock('react-use', () => { - const actual = jest.requireActual('react-use'); - - return { - ...actual, - useLocalStorage: jest.fn().mockReturnValue(['test-id', jest.fn()]), - useSessionStorage: jest.fn().mockReturnValue([undefined, jest.fn()]), - }; -}); +jest.mock('react-use/lib/useLocalStorage', () => jest.fn().mockReturnValue(['test-id', jest.fn()])); +jest.mock('react-use/lib/useSessionStorage', () => + jest.fn().mockReturnValue([undefined, jest.fn()]) +); jest.mock( '@kbn/elastic-assistant/impl/assistant/api/anonymization_fields/use_fetch_anonymization_fields', diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx index f3981696b3e80..ea5c16fc3cbba 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx @@ -16,7 +16,7 @@ import { import type { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; import { uniq } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useLocalStorage } from 'react-use'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { SecurityPageName } from '../../../common/constants'; import { HeaderPage } from '../../common/components/header_page'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx index 30ebf658f0020..c436b7ed9feb5 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx @@ -14,7 +14,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { useKibana } from '../../../lib/kibana'; import { useBreadcrumbsNav } from '../breadcrumbs'; import { SecuritySideNav } from '../security_side_nav'; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx index b1ec30833b396..bcdb9d163164c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx @@ -10,7 +10,7 @@ import { buildContextMenuForActions } from '@kbn/ui-actions-plugin/public'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { useAsync } from 'react-use'; +import useAsync from 'react-use/lib/useAsync'; import { InputsModelId } from '../../store/inputs/constants'; import { ModalInspectQuery } from '../inspect/modal'; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_host_platform.test.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_host_platform.test.ts index 1459c690068b4..c87129319597c 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_host_platform.test.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_host_platform.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { getHostPlatform } from './get_host_platform'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; diff --git a/x-pack/plugins/security_solution/public/common/mock/router.tsx b/x-pack/plugins/security_solution/public/common/mock/router.tsx index d9cf89a74db08..b946c3bd9bd5f 100644 --- a/x-pack/plugins/security_solution/public/common/mock/router.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/router.tsx @@ -5,6 +5,7 @@ * 2.0. */ +// eslint-disable-next-line no-restricted-imports import { Router } from 'react-router-dom'; // eslint-disable-next-line @kbn/eslint/module_migration import routeData from 'react-router'; diff --git a/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.test.tsx index 69f1b5fcbf4e0..6da409bcf92d9 100644 --- a/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.test.tsx @@ -16,6 +16,7 @@ import { } from './helpers'; import { renderHook } from '@testing-library/react-hooks'; import { createMemoryHistory } from 'history'; +// eslint-disable-next-line no-restricted-imports import { Router } from 'react-router-dom'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx index 08c4a8e22edfd..1b5d3784364b6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useToggle } from 'react-use'; +import useToggle from 'react-use/lib/useToggle'; import { EuiLink, EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useKibana } from '../../../../common/lib/kibana'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields_help_info.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields_help_info.tsx index 187f05880d205..9cc1a085507a7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields_help_info.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields_help_info.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useToggle } from 'react-use'; +import useToggle from 'react-use/lib/useToggle'; import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import * as defineRuleI18n from '../../../rule_creation_ui/components/step_define_rule/translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side_help_info.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side_help_info.tsx index a2b7e1a360150..e1eaa9b1e96cd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side_help_info.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side_help_info.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useToggle } from 'react-use'; +import useToggle from 'react-use/lib/useToggle'; import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx index abd3c93550694..69a00436b6992 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx @@ -6,7 +6,7 @@ */ import React, { useCallback } from 'react'; -import { useToggle } from 'react-use'; +import useToggle from 'react-use/lib/useToggle'; import { css } from '@emotion/css'; import { EuiButtonEmpty } from '@elastic/eui'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx index 766692e9efecd..51e0c5097b97d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useToggle } from 'react-use'; +import useToggle from 'react-use/lib/useToggle'; import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx index b4ff6ab29a3ff..6fbdd5b4f8910 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx @@ -16,7 +16,7 @@ import { EuiPopover, } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useBoolean } from 'react-use'; +import useBoolean from 'react-use/lib/useBoolean'; import { useUserData } from '../../../../../detections/components/user_info'; import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_install_button.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_install_button.tsx index ea83efae768fa..6ea9e9dd6a749 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_install_button.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_install_button.tsx @@ -16,7 +16,7 @@ import { EuiPopover, } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useBoolean } from 'react-use'; +import useBoolean from 'react-use/lib/useBoolean'; import type { Rule } from '../../../../rule_management/logic'; import type { RuleSignatureId } from '../../../../../../common/api/detection_engine'; import type { AddPrebuiltRulesTableActions } from './add_prebuilt_rules_table_context'; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx index e29dca9d48f3d..51ebecedac3d4 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx @@ -34,7 +34,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; -import { useToggle } from 'react-use'; +import useToggle from 'react-use/lib/useToggle'; import { PICK_ASSET_CRITICALITY } from './translations'; import { AssetCriticalityBadge } from './asset_criticality_badge'; import type { Entity, State } from './use_asset_criticality'; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx index 048b37915e0f4..835265c7402fe 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx @@ -5,8 +5,7 @@ * 2.0. */ import React from 'react'; -import { Switch } from 'react-router-dom'; -import { Route } from '@kbn/shared-ux-router'; +import { Route, Routes } from '@kbn/shared-ux-router'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; @@ -33,14 +32,14 @@ const EntityAnalyticsManagementTelemetry = () => ( const EntityAnalyticsManagementContainer: React.FC = React.memo(() => { return ( - + - + ); }); EntityAnalyticsManagementContainer.displayName = 'EntityAnalyticsManagementContainer'; @@ -56,14 +55,14 @@ const EntityAnalyticsAssetClassificationTelemetry = () => ( const EntityAnalyticsAssetClassificationContainer: React.FC = React.memo(() => { return ( - + - + ); }); diff --git a/x-pack/plugins/security_solution/public/explore/network/pages/details/index.test.tsx b/x-pack/plugins/security_solution/public/explore/network/pages/details/index.test.tsx index 57cbbb4bc65e6..19b3f653b8f14 100644 --- a/x-pack/plugins/security_solution/public/explore/network/pages/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/pages/details/index.test.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { Router, useParams } from 'react-router-dom'; +import { Router } from '@kbn/shared-ux-router'; +import { useParams } from 'react-router-dom'; import { useSourcererDataView } from '../../../../sourcerer/containers'; import { TestProviders } from '../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/management/components/privileged_route/privileged_route.test.tsx b/x-pack/plugins/security_solution/public/management/components/privileged_route/privileged_route.test.tsx index 2fdac844b5e41..32294d09ea82d 100644 --- a/x-pack/plugins/security_solution/public/management/components/privileged_route/privileged_route.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/privileged_route/privileged_route.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +// eslint-disable-next-line no-restricted-imports import { Switch, MemoryRouter } from 'react-router-dom'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts index 85647202a755c..6feaeb878790c 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts @@ -16,7 +16,7 @@ import { DefaultPolicyNotificationMessage, DefaultPolicyRuleNotificationMessage, } from '../../../../common/endpoint/models/policy_config'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { API_VERSIONS } from '@kbn/fleet-plugin/common'; const useQueryMock = _useQuery as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx index 937804565e29f..b86c79c46242d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx @@ -18,7 +18,7 @@ import { AdvancedSection } from './advanced_section'; import userEvent from '@testing-library/user-event'; import { AdvancedPolicySchema } from '../../../models/advanced_policy_schema'; import { within } from '@testing-library/react'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; jest.mock('../../../../../../common/hooks/use_license'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/attack_surface_reduction_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/attack_surface_reduction_card.test.tsx index c55f0793027e5..35cf98b5f5075 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/attack_surface_reduction_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/attack_surface_reduction_card.test.tsx @@ -17,7 +17,8 @@ import { SWITCH_LABEL, } from './attack_surface_reduction_card'; import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import userEvent from '@testing-library/user-event'; import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks'; import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/behaviour_protection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/behaviour_protection_card.test.tsx index 9a5c9db321b4b..94399b7c33c4c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/behaviour_protection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/behaviour_protection_card.test.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license'; import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license'; import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { ProtectionModes } from '../../../../../../../../common/endpoint/types'; import type { BehaviourProtectionCardProps } from './protection_seetings_card/behaviour_protection_card'; import { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/linux_event_collection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/linux_event_collection_card.test.tsx index 7be10cb5ca6d0..f28b379ce5140 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/linux_event_collection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/linux_event_collection_card.test.tsx @@ -12,7 +12,7 @@ import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endp import React from 'react'; import type { LinuxEventCollectionCardProps } from './linux_event_collection_card'; import { LinuxEventCollectionCard } from './linux_event_collection_card'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; describe('Policy Linux Event Collection Card', () => { const testSubj = getPolicySettingsFormTestSubjects('test').linuxEvents; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/mac_event_collection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/mac_event_collection_card.test.tsx index ac2c81da1c121..d951975d467f0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/mac_event_collection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/mac_event_collection_card.test.tsx @@ -10,7 +10,7 @@ import type { AppContextTestRender } from '../../../../../../../common/mock/endp import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint'; import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator'; import React from 'react'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { MacEventCollectionCardProps } from './mac_event_collection_card'; import { MacEventCollectionCard } from './mac_event_collection_card'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/malware_protections_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/malware_protections_card.test.tsx index c4060cf0d7de0..d4ca438c5e25f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/malware_protections_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/malware_protections_card.test.tsx @@ -19,7 +19,8 @@ import type { MalwareProtectionsProps } from './malware_protections_card'; import { MalwareProtectionsCard } from './malware_protections_card'; import type { PolicyConfig } from '../../../../../../../../common/endpoint/types'; import { ProtectionModes } from '../../../../../../../../common/endpoint/types'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import userEvent from '@testing-library/user-event'; jest.mock('../../../../../../../common/hooks/use_license'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/memory_protection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/memory_protection_card.test.tsx index 35ee4eb4fd2d5..4d5236a559985 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/memory_protection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/memory_protection_card.test.tsx @@ -11,7 +11,7 @@ import { createAppRootMockRenderer } from '../../../../../../../common/mock/endp import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator'; import React from 'react'; import { ProtectionModes } from '../../../../../../../../common/endpoint/types'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { MemoryProtectionCardProps } from './memory_protection_card'; import { LOCKED_CARD_MEMORY_TITLE, MemoryProtectionCard } from './memory_protection_card'; import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/ransomware_protection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/ransomware_protection_card.test.tsx index 1970c5915fe07..d78840a44e9ce 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/ransomware_protection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/ransomware_protection_card.test.tsx @@ -11,7 +11,7 @@ import { createAppRootMockRenderer } from '../../../../../../../common/mock/endp import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator'; import React from 'react'; import { ProtectionModes } from '../../../../../../../../common/endpoint/types'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks'; import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license'; import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/windows_event_collection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/windows_event_collection_card.test.tsx index 2ee20f4a51a51..4dfe847297ef2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/windows_event_collection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/cards/windows_event_collection_card.test.tsx @@ -10,7 +10,7 @@ import type { AppContextTestRender } from '../../../../../../../common/mock/endp import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint'; import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator'; import React from 'react'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { WindowsEventCollectionCardProps } from './windows_event_collection_card'; import { WindowsEventCollectionCard } from './windows_event_collection_card'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/detect_prevent_protection_level.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/detect_prevent_protection_level.test.tsx index ee31a690c27db..6616ac02e3c5a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/detect_prevent_protection_level.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/detect_prevent_protection_level.test.tsx @@ -12,7 +12,8 @@ import React from 'react'; import type { DetectPreventProtectionLevelProps } from './detect_prevent_protection_level'; import { DetectPreventProtectionLevel } from './detect_prevent_protection_level'; import userEvent from '@testing-library/user-event'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { ProtectionModes } from '../../../../../../../common/endpoint/types'; import { expectIsViewOnly, exactMatchText } from '../mocks'; import { createLicenseServiceMock } from '../../../../../../../common/license/mocks'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx index 51e2fb275a78a..ba2cd95989cde 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx @@ -17,7 +17,8 @@ import { EventCollectionCard } from './event_collection_card'; import { OperatingSystem } from '@kbn/securitysolution-utils'; import { expectIsViewOnly, exactMatchText } from '../mocks'; import userEvent from '@testing-library/user-event'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { within } from '@testing-library/react'; describe('Policy Event Collection Card common component', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.tsx index a1fee2d77b01d..8ab5f92da27c0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.tsx @@ -19,7 +19,8 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { cloneDeep, get, set } from 'lodash'; +import { cloneDeep, get } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { EuiCheckboxProps } from '@elastic/eui'; import { getEmptyValue } from '../../../../../../common/components/empty_value'; import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/notify_user_option.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/notify_user_option.test.tsx index b75084f6b97a5..eb0686d7e07ef 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/notify_user_option.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/notify_user_option.test.tsx @@ -20,7 +20,8 @@ import { NotifyUserOption, } from './notify_user_option'; import { expectIsViewOnly, exactMatchText } from '../mocks'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { ProtectionModes } from '../../../../../../../common/endpoint/types'; import userEvent from '@testing-library/user-event'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/protection_setting_card_switch.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/protection_setting_card_switch.test.tsx index 9a2bb55d85ea5..aa57720d58160 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/protection_setting_card_switch.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/protection_setting_card_switch.test.tsx @@ -16,7 +16,8 @@ import type { ProtectionSettingCardSwitchProps } from './protection_setting_card import { ProtectionSettingCardSwitch } from './protection_setting_card_switch'; import { exactMatchText, expectIsViewOnly, setMalwareMode } from '../mocks'; import { ProtectionModes } from '../../../../../../../common/endpoint/types'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import userEvent from '@testing-library/user-event'; jest.mock('../../../../../../common/hooks/use_license'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts index 9448b93c7627e..e51382a6b91a8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { PolicyConfig } from '../../../../../../common/endpoint/types'; import { AntivirusRegistrationModes, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_layout/policy_settings_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_layout/policy_settings_layout.test.tsx index 84642ffdc1582..f26d520406407 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_layout/policy_settings_layout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_layout/policy_settings_layout.test.tsx @@ -21,7 +21,8 @@ import { getPolicySettingsFormTestSubjects, setMalwareMode, } from '../policy_settings_form/mocks'; -import { cloneDeep, set } from 'lodash'; +import { cloneDeep } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { ProtectionModes } from '../../../../../../common/endpoint/types'; import { waitFor, cleanup } from '@testing-library/react'; import { packagePolicyRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; diff --git a/x-pack/plugins/security_solution/public/notes/routes.tsx b/x-pack/plugins/security_solution/public/notes/routes.tsx index 7bd17c2b012ef..c49f54f9c9a93 100644 --- a/x-pack/plugins/security_solution/public/notes/routes.tsx +++ b/x-pack/plugins/security_solution/public/notes/routes.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { Switch } from 'react-router-dom'; -import { Route } from '@kbn/shared-ux-router'; +import { Route, Routes } from '@kbn/shared-ux-router'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import { NoteManagementPage } from './pages/note_management_page'; import { SpyRoute } from '../common/utils/route/spy_routes'; @@ -26,10 +25,10 @@ const NotesManagementTelemetry = () => ( const NotesManagementContainer: React.FC = React.memo(() => { return ( - + - + ); }); NotesManagementContainer.displayName = 'NotesManagementContainer'; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/callouts/integration_card_top_callout.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/callouts/integration_card_top_callout.tsx index 27c92d0f0b11f..3a6b5ae3be92c 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/callouts/integration_card_top_callout.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/callouts/integration_card_top_callout.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { useOnboardingService } from '../../../../../hooks/use_onboarding_service'; import { AgentlessAvailableCallout } from './agentless_available_callout'; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.test.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.test.ts index 19e80e4005a59..775ff09546fe6 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.test.ts +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.test.ts @@ -7,7 +7,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useBodyConfig } from './use_body_config'; import { useKibana } from '../../../../common/lib/kibana/kibana_react'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { hasCapabilities } from '../../../../common/lib/capabilities'; const bodyConfig = [ @@ -43,7 +43,7 @@ const bodyConfig = [ ]; // Mock dependencies -jest.mock('react-use'); +jest.mock('react-use/lib/useObservable'); jest.mock('../../../../common/lib/kibana/kibana_react'); jest.mock('../../../../common/lib/capabilities'); jest.mock('../body_config', () => ({ bodyConfig })); diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.ts index e140f953fb028..f7b12e5988c0d 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.ts +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_body_config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { useMemo } from 'react'; import { hasCapabilities } from '../../../../common/lib/capabilities'; import { useKibana } from '../../../../common/lib/kibana/kibana_react'; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/teammates_card/teammates_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/teammates_card/teammates_card.tsx index da316e0d0d907..a79b288dd8562 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/teammates_card/teammates_card.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/teammates_card/teammates_card.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { useOnboardingService } from '../../../../hooks/use_onboarding_service'; import { LinkCard } from '../common/link_card'; import teammatesImage from './images/teammates_card.png'; diff --git a/x-pack/plugins/security_solution/public/onboarding/hooks/use_stored_state.ts b/x-pack/plugins/security_solution/public/onboarding/hooks/use_stored_state.ts index c22c8f0f5310c..eac269f3a4a35 100644 --- a/x-pack/plugins/security_solution/public/onboarding/hooks/use_stored_state.ts +++ b/x-pack/plugins/security_solution/public/onboarding/hooks/use_stored_state.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useLocalStorage } from 'react-use'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import type { OnboardingCardId } from '../constants'; import type { IntegrationTabId } from '../components/onboarding_body/cards/integrations/types'; diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts index 58e8aced4470b..a0d0ab4dd1061 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { get, has, set, omit, isObject, toString as fpToString } from 'lodash/fp'; +import { get, has, omit, isObject, toString as fpToString } from 'lodash/fp'; +import { set } from '@kbn/safer-lodash-set/fp'; import type { Action, Middleware } from 'redux'; import type { CoreStart } from '@kbn/core/public'; import type { Filter, MatchAllFilter } from '@kbn/es-query'; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 66b804e07eb10..b3011005a8b76 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -71,7 +71,8 @@ import type { HapiReadableStream, SecuritySolutionRequestHandlerContext } from ' import { createHapiReadableStreamMock } from '../../services/actions/mocks'; import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; -import { omit, set } from 'lodash'; +import { omit } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; import { responseActionsClientMock } from '../../services/actions/clients/mocks'; import type { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts index 20389d41f3956..dafc0b285f489 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts @@ -36,7 +36,7 @@ import { ENDPOINT_ACTIONS_INDEX, } from '../../../../../../common/endpoint/constants'; import type { DeepMutable } from '../../../../../../common/endpoint/types/utility_types'; -import { set } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import { responseActionsClientMock } from '../mocks'; import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; import { getResponseActionFeatureKey } from '../../../feature_usage/feature_keys'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts index c9720a139ae7d..128cabf02aca6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts @@ -8,7 +8,8 @@ import { ecsFieldMap } from '@kbn/alerts-as-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; -import { isPlainObject, isArray, set } from 'lodash'; +import { isPlainObject, isArray } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { SearchTypes } from '../../../../../../common/detection_engine/types'; import { isValidIpType } from './ecs_types_validators/is_valid_ip_type'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index cb33d608ea0c9..0f29a415dfeba 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -8,7 +8,8 @@ import moment from 'moment'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/package_policy'; -import { merge, set } from 'lodash'; +import { merge } from 'lodash'; +import { set } from '@kbn/safer-lodash-set'; import type { Logger, LogMeta } from '@kbn/core/server'; import { sha256 } from 'js-sha256'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; From bdc9ce932bbfa606dd1f1e188c8b32df4327a0a4 Mon Sep 17 00:00:00 2001 From: Ilya Nikokoshev Date: Tue, 15 Oct 2024 15:02:00 +0300 Subject: [PATCH 18/84] [Auto Import] Improve log format recognition (#196228) Previously the LLM would often select `unstructured` format for what (to our eye) clearly are CSV samples. We add the missing line break between the log samples (which should help format recognition in general) and change the prompt to clarify when the comma-separated list should be treated as a `csv` and when as `structured` format. See GitHub for examples. --------- Co-authored-by: Bharat Pasupula <123897612+bhapas@users.noreply.github.com> --- .../graphs/log_type_detection/detection.ts | 2 +- .../graphs/log_type_detection/prompts.ts | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts index a8334432a0211..c0172f2d139d0 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts @@ -26,7 +26,7 @@ export async function handleLogFormatDetection({ const logFormatDetectionResult = await logFormatDetectionNode.invoke({ ex_answer: state.exAnswer, - log_samples: samples, + log_samples: samples.join('\n'), package_title: state.packageTitle, datastream_title: state.dataStreamTitle, }); diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/prompts.ts index 71246d46363cb..b6e777a87888a 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/prompts.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/prompts.ts @@ -17,16 +17,18 @@ export const LOG_FORMAT_DETECTION_PROMPT = ChatPromptTemplate.fromMessages([ The samples apply to the data stream {datastream_title} inside the integration package {package_title}. Follow these steps to do this: -1. Go through each log sample and identify the log format. Output this as "name: ". -2. If the samples have any or all of priority, timestamp, loglevel, hostname, ipAddress, messageId in the beginning information then set "header: true". -3. If the samples have a syslog header then set "header: true" , else set "header: false". If you are unable to determine the syslog header presence then set "header: false". -4. If the log samples have structured message body with key-value pairs then classify it as "name: structured". Look for a flat list of key-value pairs, often separated by spaces, commas, or other delimiters. -5. Consider variations in formatting, such as quotes around values ("key=value", key="value"), special characters in keys or values, or escape sequences. -6. If the log samples have unstructured body like a free-form text then classify it as "name: unstructured". -7. If the log samples follow a csv format then classify it with "name: csv". There are two sub-cases for csv: - a. If there is a csv header then set "header: true". - b. If there is no csv header then set "header: false" and try to find good names for the columns in the "columns" array by looking into the values of data in those columns. For each column, if you are unable to find good name candidate for it then output an empty string, like in the example. -8. If you cannot put the format into any of the above categories then classify it with "name: unsupported". +1. Go through each log sample and identify the log format. Output this as "name: ". Here are the values for log_format: + * 'csv': If the log samples follow a Comma-Separated Values format then classify it with "name: csv". There are two sub-cases for csv: + a. If there is a csv header then set "header: true". + b. If there is no csv header then set "header: false" and try to find good names for the columns in the "columns" array by looking into the values of data in those columns. For each column, if you are unable to find good name candidate for it then output an empty string, like in the example. + * 'structured': If the log samples have structured message body with key-value pairs then classify it as "name: structured". Look for a flat list of key-value pairs, often separated by some delimiters. Consider variations in formatting, such as quotes around values ("key=value", key="value"), special characters in keys or values, or escape sequences. + * 'unstructured': If the log samples have unstructured body like a free-form text then classify it as "name: unstructured". + * 'unsupported': If you cannot put the format into any of the above categories then classify it with "name: unsupported". +2. Header: for structured and unstructured format: + - if the samples have any or all of priority, timestamp, loglevel, hostname, ipAddress, messageId in the beginning information then set "header: true". + - if the samples have a syslog header then set "header: true" + - else set "header: false". If you are unable to determine the syslog header presence then set "header: false". +3. Note that a comma-separated list should be classified as 'csv' if its rows only contain values separated by commas. But if it looks like a list of comma separated key-values pairs like 'key1=value1, key2=value2' it should be classified as 'structured'. You ALWAYS follow these guidelines when writing your response: From 63e116bb078c29c70e4e23cba1c88d0ac022801d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Gonz=C3=A1lez?= Date: Tue, 15 Oct 2024 14:09:30 +0200 Subject: [PATCH 19/84] [Search] New search connector creation flow (#187582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR brings a new and dedicated search connector creation flow for ES3 and ESS. [Figma Prototype](https://www.figma.com/proto/eKQr4HYlz0v9pTofRPWIyH/Ingestion-methods-flow?page-id=411%3A158867&node-id=411-158870&viewport=3831%2C-1905%2C1.23&t=ZP9e3LtaSeJ5FMAz-9&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=411%3A158870&show-proto-sidebar=1) ![CleanShot 2024-07-04 at 16 27 21](https://github.com/elastic/kibana/assets/3108788/45e61110-f222-4bad-b24d-87ebad07ca98) ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Efe Gürkan YALAMAN Co-authored-by: Elastic Machine Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../configuration/connector_configuration.tsx | 8 + .../connector_configuration_form.tsx | 48 ++- .../enterprise_search/common/constants.ts | 54 +++ .../api/connector/add_connector_api_logic.ts | 14 +- .../generate_connector_config_api_logic.ts | 4 +- .../generate_connector_names_api_logic.ts | 23 +- .../components/generate_config_button.tsx | 5 +- .../components/generated_config_fields.tsx | 48 +-- .../connector_detail/deployment.tsx | 31 +- .../connector_detail/deployment_logic.ts | 5 +- .../components/connectors/connectors.tsx | 4 +- .../connectors/connectors_router.tsx | 8 +- .../assets/connector_logo.svg | 11 + .../assets/connector_logos_comp.png | Bin 0 -> 80544 bytes .../choose_connector_selectable.tsx | 172 +++++++++ .../connector_description_popover.tsx | 166 +++++++++ .../components/manual_configuration.tsx | 114 ++++++ .../manual_configuration_flyout.tsx | 228 ++++++++++++ .../create_connector/configuration_step.tsx | 122 ++++++ .../create_connector/create_connector.tsx | 265 +++++++++++++ .../create_connector/deployment_step.tsx | 83 +++++ .../create_connector/finish_up_step.tsx | 348 ++++++++++++++++++ .../connectors/create_connector/index.ts | 8 + .../create_connector/start_step.tsx | 340 +++++++++++++++++ .../connectors/utils/generate_step_state.ts | 29 ++ .../method_connector/new_connector_logic.ts | 244 +++++++++--- .../new_connector_template.tsx | 44 +-- .../enterprise_search_content/routes.ts | 1 + .../applications/shared/constants/actions.ts | 4 + .../lib/connectors/generate_connector_name.ts | 47 ++- .../routes/enterprise_search/connectors.ts | 10 +- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 34 files changed, 2336 insertions(+), 158 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logo.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logos_comp.png create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector_selectable.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/connector_description_popover.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration_flyout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/configuration_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/create_connector.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/deployment_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/finish_up_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/utils/generate_step_state.ts diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx index 34cb1a4b0ed7a..8cb83176a6591 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx @@ -45,6 +45,7 @@ interface ConnectorConfigurationProps { hasPlatinumLicense: boolean; isLoading: boolean; saveConfig: (configuration: Record) => void; + saveAndSync?: (configuration: Record) => void; stackManagementLink?: string; subscriptionLink?: string; children?: React.ReactNode; @@ -90,6 +91,7 @@ export const ConnectorConfigurationComponent: FC< hasPlatinumLicense, isLoading, saveConfig, + saveAndSync, subscriptionLink, stackManagementLink, }) => { @@ -166,6 +168,12 @@ export const ConnectorConfigurationComponent: FC< saveConfig(config); setIsEditing(false); }} + {...(saveAndSync && { + saveAndSync: (config) => { + saveAndSync(config); + setIsEditing(false); + }, + })} /> ) : ( uncategorizedDisplayList.length > 0 && ( diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration_form.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration_form.tsx index f7e619f407f12..9b83f7c0d3302 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration_form.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration_form.tsx @@ -36,6 +36,7 @@ interface ConnectorConfigurationForm { isLoading: boolean; isNative: boolean; saveConfig: (config: Record) => void; + saveAndSync?: (config: Record) => void; stackManagementHref?: string; subscriptionLink?: string; } @@ -60,6 +61,7 @@ export const ConnectorConfigurationForm: React.FC = isLoading, isNative, saveConfig, + saveAndSync, }) => { const [localConfig, setLocalConfig] = useState(configuration); const [configView, setConfigView] = useState( @@ -167,19 +169,7 @@ export const ConnectorConfigurationForm: React.FC = )} - - - - {i18n.translate('searchConnectors.configurationConnector.config.submitButton.title', { - defaultMessage: 'Save configuration', - })} - - + = )} + + + {i18n.translate('searchConnectors.configurationConnector.config.submitButton.title', { + defaultMessage: 'Save', + })} + + + {saveAndSync && ( + + { + saveAndSync(configViewToConfigValues(configView)); + }} + > + {i18n.translate( + 'searchConnectors.configurationConnector.config.submitButton.title', + { + defaultMessage: 'Save and sync', + } + )} + + + )} diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 795237ef9b427..4da0244b2ec5e 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import dedent from 'dedent'; + import { ENTERPRISE_SEARCH_APP_ID, ENTERPRISE_SEARCH_CONTENT_APP_ID, @@ -210,6 +212,58 @@ export const SEARCH_RELEVANCE_PLUGIN = { SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', }; +export const CREATE_CONNECTOR_PLUGIN = { + CLI_SNIPPET: dedent`./bin/connectors connector create + --index-name my-index + --index-language en + --from-file config.yml + `, + CONSOLE_SNIPPET: dedent`# Create an index +PUT /my-index-000001 +{ + "settings": { + "index": { + "number_of_shards": 3, + "number_of_replicas": 2 + } + } +} + +# Create an API key +POST /_security/api_key +{ + "name": "my-api-key", + "expiration": "1d", + "role_descriptors": + { + "role-a": { + "cluster": ["all"], + "indices": [ + { + "names": ["index-a*"], + "privileges": ["read"] + } + ] + }, + "role-b": { + "cluster": ["all"], + "indices": [ + { + "names": ["index-b*"], + "privileges": ["all"] + }] + } + }, "metadata": + { "application": "my-application", + "environment": { + "level": 1, + "trusted": true, + "tags": ["dev", "staging"] + } + } + }`, +}; + export const LICENSED_SUPPORT_URL = 'https://support.elastic.co'; export const JSON_HEADER = { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/add_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/add_connector_api_logic.ts index be8e23bdca1c5..3593a7b123533 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/add_connector_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/add_connector_api_logic.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; interface AddConnectorValue { @@ -20,11 +20,17 @@ export interface AddConnectorApiLogicArgs { language: string | null; name: string; serviceType?: string; + // Without a proper refactoring there is no good way to chain actions. + // This prop is simply passed back with the result to let listeners + // know what was the intent of the request. And call the next action + // accordingly. + uiFlags?: Record; } export interface AddConnectorApiLogicResponse { id: string; indexName: string; + uiFlags?: Record; } export const addConnector = async ({ @@ -34,6 +40,7 @@ export const addConnector = async ({ isNative, language, serviceType, + uiFlags, }: AddConnectorApiLogicArgs): Promise => { const route = '/internal/enterprise_search/connectors'; @@ -54,7 +61,12 @@ export const addConnector = async ({ return { id: result.id, indexName: result.index_name, + uiFlags, }; }; export const AddConnectorApiLogic = createApiLogic(['add_connector_api_logic'], addConnector); +export type AddConnectorApiLogicActions = Actions< + AddConnectorApiLogicArgs, + AddConnectorApiLogicResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts index 21edf734bc230..449d3f6628648 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts @@ -5,13 +5,15 @@ * 2.0. */ -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export interface GenerateConfigApiArgs { connectorId: string; } +export type GenerateConfigApiActions = Actions; + export const generateConnectorConfig = async ({ connectorId }: GenerateConfigApiArgs) => { const route = `/internal/enterprise_search/connectors/${connectorId}/generate_config`; return await HttpLogic.values.http.post(route); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_names_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_names_api_logic.ts index 5583c8c8e22e4..8d2ee0ee87aa3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_names_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_names_api_logic.ts @@ -4,23 +4,38 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export interface GenerateConnectorNamesApiArgs { + connectorName?: string; connectorType?: string; } +export interface GenerateConnectorNamesApiResponse { + apiKeyName: string; + connectorName: string; + indexName: string; +} + export const generateConnectorNames = async ( - { connectorType }: GenerateConnectorNamesApiArgs = { connectorType: 'custom' } + { connectorType, connectorName }: GenerateConnectorNamesApiArgs = { connectorType: 'custom' } ) => { + if (connectorType === '') { + connectorType = 'custom'; + } const route = `/internal/enterprise_search/connectors/generate_connector_name`; return await HttpLogic.values.http.post(route, { - body: JSON.stringify({ connectorType }), + body: JSON.stringify({ connectorName, connectorType }), }); }; export const GenerateConnectorNamesApiLogic = createApiLogic( - ['generate_config_api_logic'], + ['generate_connector_names_api_logic'], generateConnectorNames ); + +export type GenerateConnectorNamesApiLogicActions = Actions< + GenerateConnectorNamesApiArgs, + GenerateConnectorNamesApiResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx index bb34d652ee74d..ed28ba575d824 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx @@ -12,13 +12,15 @@ import { i18n } from '@kbn/i18n'; export interface GenerateConfigButtonProps { connectorId: string; + disabled?: boolean; generateConfiguration: (params: { connectorId: string }) => void; isGenerateLoading: boolean; } export const GenerateConfigButton: React.FC = ({ connectorId, + disabled, generateConfiguration, - isGenerateLoading, + isGenerateLoading = false, }) => { return ( @@ -26,6 +28,7 @@ export const GenerateConfigButton: React.FC = ({ void; + generateApiKey?: () => void; isGenerateLoading: boolean; } @@ -93,7 +93,7 @@ export const GeneratedConfigFields: React.FC = ({ }; const onConfirm = () => { - generateApiKey(); + if (generateApiKey) generateApiKey(); setIsModalVisible(false); }; @@ -222,16 +222,18 @@ export const GeneratedConfigFields: React.FC = ({ {apiKey?.encoded} - - - + {generateApiKey && ( + + + + )} = ({ ) : ( - - - + generateApiKey && ( + + + + ) )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx index 2c20902793093..e3bd0e867af3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx @@ -61,6 +61,22 @@ export const ConnectorDeployment: React.FC = () => { Record >('search:connector-ui-options', {}); + useEffect(() => { + if (connectorId && connector && connector.api_key_id) { + getApiKeyById(connector.api_key_id); + } + }, [connector, connectorId]); + + const selectDeploymentMethod = (deploymentMethod: 'docker' | 'source') => { + if (connector) { + setSelectedDeploymentMethod(deploymentMethod); + setConnectorUiOptions({ + ...connectorUiOptions, + [connector.id]: { deploymentMethod }, + }); + } + }; + useEffect(() => { if (connectorUiOptions && connectorId && connectorUiOptions[connectorId]) { setSelectedDeploymentMethod(connectorUiOptions[connectorId].deploymentMethod); @@ -68,25 +84,10 @@ export const ConnectorDeployment: React.FC = () => { selectDeploymentMethod('docker'); } }, [connectorUiOptions, connectorId]); - - useEffect(() => { - if (connectorId && connector && connector.api_key_id) { - getApiKeyById(connector.api_key_id); - } - }, [connector, connectorId]); - if (!connector || connector.is_native) { return <>; } - const selectDeploymentMethod = (deploymentMethod: 'docker' | 'source') => { - setSelectedDeploymentMethod(deploymentMethod); - setConnectorUiOptions({ - ...connectorUiOptions, - [connector.id]: { deploymentMethod }, - }); - }; - const hasApiKey = !!(connector.api_key_id ?? generatedData?.apiKey); const isWaitingForConnector = !connector.status || connector.status === ConnectorStatus.CREATED; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts index 09c2c8db48e03..13f3cc0b30369 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts @@ -10,15 +10,12 @@ import { kea, MakeLogicType } from 'kea'; import { Connector } from '@kbn/search-connectors'; import { HttpError, Status } from '../../../../../common/types/api'; -import { Actions } from '../../../shared/api_logic/create_api_logic'; import { - GenerateConfigApiArgs, + GenerateConfigApiActions, GenerateConfigApiLogic, } from '../../api/connector/generate_connector_config_api_logic'; import { APIKeyResponse } from '../../api/generate_api_key/generate_api_key_logic'; -type GenerateConfigApiActions = Actions; - export interface DeploymentLogicValues { generateConfigurationError: HttpError; generateConfigurationStatus: Status; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx index a29f6c540b7ce..c12dd8036b6b9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx @@ -44,8 +44,8 @@ import { ConnectorStats } from './connector_stats'; import { ConnectorsLogic } from './connectors_logic'; import { ConnectorsTable } from './connectors_table'; import { CrawlerEmptyState } from './crawler_empty_state'; +import { CreateConnector } from './create_connector'; import { DeleteConnectorModal } from './delete_connector_modal'; -import { SelectConnector } from './select_connector/select_connector'; export const connectorsBreadcrumbs = [ i18n.translate('xpack.enterpriseSearch.content.connectors.breadcrumb', { @@ -81,7 +81,7 @@ export const Connectors: React.FC = ({ isCrawler }) => { }, [searchParams.from, searchParams.size, searchQuery, isCrawler]); return !isLoading && isEmpty && !isCrawler ? ( - + ) : ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_router.tsx index dc5ed0342c3be..9020a1d165168 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_router.tsx @@ -13,23 +13,27 @@ import { CONNECTORS_PATH, NEW_INDEX_SELECT_CONNECTOR_PATH, NEW_CONNECTOR_PATH, + NEW_CONNECTOR_FLOW_PATH, CONNECTOR_DETAIL_PATH, } from '../../routes'; import { ConnectorDetailRouter } from '../connector_detail/connector_detail_router'; import { NewSearchIndexPage } from '../new_index/new_search_index_page'; import { Connectors } from './connectors'; -import { SelectConnector } from './select_connector/select_connector'; +import { CreateConnector } from './create_connector'; export const ConnectorsRouter: React.FC = () => { return ( - + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logo.svg b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logo.svg new file mode 100644 index 0000000000000..f827c8dce36eb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logos_comp.png b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/assets/connector_logos_comp.png new file mode 100644 index 0000000000000000000000000000000000000000..22f5ad4c31a315f9920fc0d84222fb8f9b855886 GIT binary patch literal 80544 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

=Fn_|x6KHk707#Dy1LSTt(f#G>+f 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 Ef`D = ({ + selfManaged, +}) => { + const { euiTheme } = useEuiTheme(); + const [isOpen, setIsOpen] = useState(false); + const [selectableOptions, selectableSetOptions] = useState< + Array> + >([]); + const { connectorTypes } = useValues(KibanaLogic); + const allConnectors = useMemo( + () => connectorTypes.sort((a, b) => a.name.localeCompare(b.name)), + [connectorTypes] + ); + const { selectedConnector } = useValues(NewConnectorLogic); + const { setSelectedConnector } = useActions(NewConnectorLogic); + + const getInitialOptions = () => { + return allConnectors.map((connector, key) => { + const append: JSX.Element[] = []; + if (connector.isTechPreview) { + append.push( + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.thechPreviewBadgeLabel', + { defaultMessage: 'Tech preview' } + )} + + ); + } + if (connector.isBeta) { + append.push( + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.BetaBadgeLabel', + { + defaultMessage: 'Beta', + } + )} + + ); + } + if (selfManaged === 'native' && !connector.isNative) { + append.push( + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.OnlySelfManagedBadgeLabel', + { + defaultMessage: 'Self managed', + } + )} + + ); + } + + return { + append, + key: key.toString(), + label: connector.name, + prepend: , + }; + }); + }; + + const initialOptions = getInitialOptions(); + + useEffect(() => { + selectableSetOptions(initialOptions); + }, [selfManaged]); + const [searchValue, setSearchValue] = useState(''); + + const openPopover = useCallback(() => { + setIsOpen(true); + }, []); + const closePopover = useCallback(() => { + setIsOpen(false); + }, []); + + return ( + + { + selectableSetOptions(newOptions); + closePopover(); + if (changedOption.checked === 'on') { + const keySelected = Number(changedOption.key); + setSelectedConnector(allConnectors[keySelected]); + setSearchValue(allConnectors[keySelected].name); + } else { + setSelectedConnector(null); + setSearchValue(''); + } + }} + listProps={{ + isVirtualized: true, + rowHeight: Number(euiTheme.base * 3), + showIcons: false, + }} + singleSelection + searchable + searchProps={{ + fullWidth: true, + isClearable: true, + onChange: (value) => { + if (value !== selectedConnector?.name) { + setSearchValue(value); + } + }, + onClick: openPopover, + onFocus: openPopover, + placeholder: i18n.translate( + 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text', + { defaultMessage: 'Choose a data source' } + ), + value: searchValue, + }} + > + {(list, search) => ( + + {list} + + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/connector_description_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/connector_description_popover.tsx new file mode 100644 index 0000000000000..b19a5ac8ddba5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/connector_description_popover.tsx @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState } from 'react'; + +import { + EuiButtonIcon, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiPopover, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import connectorLogo from '../../../../../../assets/images/connector_logo_network_drive_version.svg'; + +const nativePopoverPanels = [ + { + description: i18n.translate( + 'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.native.chooseADataSourceLabel', + { defaultMessage: 'Choose a data source you would like to sync' } + ), + icons: [], + id: 'native-choose-source', + }, + { + description: i18n.translate( + 'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.native.configureConnectorLabel', + { defaultMessage: 'Configure your connector using our Kibana UI' } + ), + icons: [, ], + id: 'native-configure-connector', + }, +]; + +const connectorClientPopoverPanels = [ + { + description: i18n.translate( + 'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.client.chooseADataSourceLabel', + { defaultMessage: 'Choose a data source you would like to sync' } + ), + icons: [], + id: 'client-choose-source', + }, + { + description: i18n.translate( + 'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.client.configureConnectorLabel', + { + defaultMessage: + 'Deploy connector code on your own infrastructure by running from source or using Docker', + } + ), + icons: [ + , + , + , + ], + id: 'client-deploy', + }, + { + description: i18n.translate( + 'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.client.enterDetailsLabel', + { + defaultMessage: 'Enter access and connection details for your data source', + } + ), + icons: [ + , + , + , + , + , + ], + id: 'client-configure-connector', + }, +]; + +export interface ConnectorDescriptionPopoverProps { + isDisabled: boolean; + isNative: boolean; +} + +export const ConnectorDescriptionPopover: React.FC = ({ + isNative, + isDisabled, +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const panels = isNative ? nativePopoverPanels : connectorClientPopoverPanels; + return ( + setIsPopoverOpen(!isPopoverOpen)} + /> + } + isOpen={isPopoverOpen} + closePopover={() => { + setIsPopoverOpen(false); + }} + > + + {isDisabled && ( + + + + + + )} + + + {panels.map((panel) => { + return ( + + + + + {panel.icons.map((icon, index) => ( + + {icon} + + ))} + + + + +

{panel.description}

+ + + + + ); + })} + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration.tsx new file mode 100644 index 0000000000000..13273266a2068 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState } from 'react'; + +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + useGeneratedHtmlId, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { SelfManagePreference } from '../create_connector'; + +import { ManualConfigurationFlyout } from './manual_configuration_flyout'; + +export interface ManualConfigurationProps { + isDisabled: boolean; + selfManagePreference: SelfManagePreference; +} + +export const ManualConfiguration: React.FC = ({ + isDisabled, + selfManagePreference, +}) => { + const [isPopoverOpen, setPopover] = useState(false); + const splitButtonPopoverId = useGeneratedHtmlId({ + prefix: 'splitButtonPopover', + }); + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [flyoutContent, setFlyoutContent] = useState<'manual_config' | 'client'>(); + + const items = [ + { + setFlyoutContent('manual_config'); + setIsFlyoutVisible(true); + closePopover(); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.manageAttachedIndexContextMenuItemLabel', + { defaultMessage: 'Manual configuration' } + )} + , + { + setFlyoutContent('client'); + setIsFlyoutVisible(true); + closePopover(); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.scheduleASyncContextMenuItemLabel', + { + defaultMessage: 'Try with CLI', + } + )} + , + ]; + + return ( + <> + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + {isFlyoutVisible && ( + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration_flyout.tsx new file mode 100644 index 0000000000000..6fc80ec3a81f1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/manual_configuration_flyout.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiButtonEmpty, + EuiCode, + EuiCodeBlock, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiFormRow, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { CREATE_CONNECTOR_PLUGIN } from '../../../../../../../common/constants'; +import { NewConnectorLogic } from '../../../new_index/method_connector/new_connector_logic'; + +import { SelfManagePreference } from '../create_connector'; + +const CLI_LABEL = i18n.translate( + 'xpack.enterpriseSearch.createConnector.manualConfiguration.cliLabel', + { + defaultMessage: 'CLI', + } +); + +export interface ManualConfigurationFlyoutProps { + flyoutContent: string | undefined; + selfManagePreference: SelfManagePreference; + setIsFlyoutVisible: (value: boolean) => void; +} +export const ManualConfigurationFlyout: React.FC = ({ + flyoutContent, + selfManagePreference, + setIsFlyoutVisible, +}) => { + const simpleFlyoutTitleId = useGeneratedHtmlId({ + prefix: 'simpleFlyoutTitle', + }); + + const { connectorName } = useValues(NewConnectorLogic); + const { setRawName, createConnector } = useActions(NewConnectorLogic); + + return ( + setIsFlyoutVisible(false)} + aria-labelledby={simpleFlyoutTitleId} + size="s" + > + {flyoutContent === 'manual_config' && ( + <> + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.manualConfiguration.h2.cliLabel', + { + defaultMessage: 'Manual configuration', + } + )} +

+
+ + +

+ + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.manualConfiguration.generateConfigLinkLabel', + { + defaultMessage: 'Generate configuration', + } + )} + + ), + }} + /> +

+
+
+ + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.manualConfiguration.connectorName', + { + defaultMessage: 'Connector', + } + )} +

+
+ + + { + setRawName(e.target.value); + }} + /> + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.manualConfiguration.p.connectorNameDescription', + { + defaultMessage: + 'You will be redirected to the connector page to configure the rest of your connector', + } + )} +

+
+
+
+
+ + + + setIsFlyoutVisible(false)} + flush="left" + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.flyoutManualConfigContent.closeButtonEmptyLabel', + { defaultMessage: 'Close' } + )} + + + + { + createConnector({ + isSelfManaged: selfManagePreference === 'selfManaged', + shouldGenerateAfterCreate: false, + shouldNavigateToConnectorAfterCreate: true, + }); + }} + fill + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.flyoutManualConfigContent.saveConfigurationButtonLabel', + { defaultMessage: 'Save configuration' } + )} + + + + + + )} + {flyoutContent === 'client' && ( + <> + + +

{CLI_LABEL}

+
+
+ + +

+ + {CLI_LABEL} + + ), + myIndex: my-index, + }} + /> +

+
+ + + {CREATE_CONNECTOR_PLUGIN.CLI_SNIPPET} + +
+ + )} +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/configuration_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/configuration_step.tsx new file mode 100644 index 0000000000000..8644cd72f53d3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/configuration_step.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTitle, + EuiText, + EuiButton, + EuiProgress, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { ConnectorConfigurationComponent, ConnectorStatus } from '@kbn/search-connectors'; + +import { Status } from '../../../../../../common/types/api'; + +import * as Constants from '../../../../shared/constants'; +import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic'; +import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; + +interface ConfigurationStepProps { + setCurrentStep: Function; + title: string; +} + +export const ConfigurationStep: React.FC = ({ title, setCurrentStep }) => { + const { connector } = useValues(ConnectorViewLogic); + const { updateConnectorConfiguration } = useActions(ConnectorViewLogic); + const { status } = useValues(ConnectorConfigurationApiLogic); + const isSyncing = false; + + const isNextStepEnabled = + connector?.status === ConnectorStatus.CONNECTED || + connector?.status === ConnectorStatus.CONFIGURED; + + useEffect(() => { + setTimeout(() => { + window.scrollTo({ + behavior: 'smooth', + top: 0, + }); + }, 100); + }, []); + + if (!connector) return null; + + return ( + <> + + + + +

{title}

+
+ + { + updateConnectorConfiguration({ + configuration: config, + connectorId: connector.id, + }); + }} + /> + + {isSyncing && ( + + )} +
+
+ + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.configurationStep.h4.finishUpLabel', + { + defaultMessage: 'Finish up', + } + )} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.configurationStep.p.description', + { + defaultMessage: + 'You can manually sync your data, schedule a recurring sync or manage your domains.', + } + )} +

+
+ + setCurrentStep('finish')} + fill + > + {Constants.NEXT_BUTTON_LABEL} + +
+
+
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/create_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/create_connector.tsx new file mode 100644 index 0000000000000..e8cef81662096 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/create_connector.tsx @@ -0,0 +1,265 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; + +import { css } from '@emotion/react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiSteps, + EuiSuperSelect, + EuiText, + useEuiTheme, +} from '@elastic/eui'; + +import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; +import { i18n } from '@kbn/i18n'; + +import { AddConnectorApiLogic } from '../../../api/connector/add_connector_api_logic'; +import { EnterpriseSearchContentPageTemplate } from '../../layout'; +import { NewConnectorLogic } from '../../new_index/method_connector/new_connector_logic'; +import { errorToText } from '../../new_index/utils/error_to_text'; +import { connectorsBreadcrumbs } from '../connectors'; + +import { generateStepState } from '../utils/generate_step_state'; + +import connectorsBackgroundImage from './assets/connector_logos_comp.png'; + +import { ConfigurationStep } from './configuration_step'; +import { DeploymentStep } from './deployment_step'; +import { FinishUpStep } from './finish_up_step'; +import { StartStep } from './start_step'; + +export type ConnectorCreationSteps = 'start' | 'deployment' | 'configure' | 'finish'; +export type SelfManagePreference = 'native' | 'selfManaged'; +export const CreateConnector: React.FC = () => { + const { error } = useValues(AddConnectorApiLogic); + const { euiTheme } = useEuiTheme(); + const [selfManagePreference, setSelfManagePreference] = useState('native'); + + const { selectedConnector, currentStep } = useValues(NewConnectorLogic); + const { setCurrentStep } = useActions(NewConnectorLogic); + const stepStates = generateStepState(currentStep); + + useEffect(() => { + // TODO: separate this to ability and preference + if (!selectedConnector?.isNative || !selfManagePreference) { + setSelfManagePreference('selfManaged'); + } else { + setSelfManagePreference('native'); + } + }, [selectedConnector]); + + const getSteps = (selfManaged: boolean): EuiContainedStepProps[] => { + return [ + { + children: null, + status: stepStates.start, + title: i18n.translate('xpack.enterpriseSearch.createConnector.startStep.startLabel', { + defaultMessage: 'Start', + }), + }, + ...(selfManaged + ? [ + { + children: null, + status: stepStates.deployment, + title: i18n.translate( + 'xpack.enterpriseSearch.createConnector.deploymentStep.deploymentLabel', + { defaultMessage: 'Deployment' } + ), + }, + ] + : []), + { + children: null, + status: stepStates.configure, + title: i18n.translate( + 'xpack.enterpriseSearch.createConnector.configurationStep.configurationLabel', + { defaultMessage: 'Configuration' } + ), + }, + + { + children: null, + status: stepStates.finish, + title: i18n.translate('xpack.enterpriseSearch.createConnector.finishUpStep.finishUpLabel', { + defaultMessage: 'Finish up', + }), + }, + ]; + }; + + const stepContent: Record<'start' | 'deployment' | 'configure' | 'finish', React.ReactNode> = { + configure: ( + + ), + deployment: , + finish: ( + + ), + start: ( + { + setSelfManagePreference(preference); + }} + error={errorToText(error)} + /> + ), + }; + + return ( + + + {/* Col 1 */} + + + css` + .euiStep__content { + padding-block-end: ${euiTheme.size.xs}; + } + `} + /> + + {selectedConnector?.docsUrl && selectedConnector?.docsUrl !== '' && ( + <> + +

+ + {'Elastic '} + {selectedConnector?.name} + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.connectorDocsLinkLabel', + { defaultMessage: ' connector reference' } + )} + +

+
+ + + )} + {currentStep !== 'start' && ( + <> + + + + {selectedConnector?.name} + + ), + value: 'item1', + }, + ]} + /> + + + + {selfManagePreference + ? i18n.translate( + 'xpack.enterpriseSearch.createConnector.badgeType.selfManaged', + { + defaultMessage: 'Self managed', + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.createConnector.badgeType.ElasticManaged', + { + defaultMessage: 'Elastic managed', + } + )} + + + )} +
+
+ {/* Col 2 */} + {stepContent[currentStep]} +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/deployment_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/deployment_step.tsx new file mode 100644 index 0000000000000..6e5245f072b4b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/deployment_step.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useValues } from 'kea'; + +import { EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiButton, EuiFlexGroup } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { ConnectorStatus } from '@kbn/search-connectors'; + +import * as Constants from '../../../../shared/constants'; +import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; +import { ConnectorDeployment } from '../../connector_detail/deployment'; + +interface DeploymentStepProps { + setCurrentStep: Function; +} + +export const DeploymentStep: React.FC = ({ setCurrentStep }) => { + const { connector } = useValues(ConnectorViewLogic); + const isNextStepEnabled = + connector && !(!connector.status || connector.status === ConnectorStatus.CREATED); + + useEffect(() => { + setTimeout(() => { + window.scrollTo({ + behavior: 'smooth', + top: 0, + }); + }, 100); + }, []); + return ( + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.DeploymentStep.Configuration.title', + { + defaultMessage: 'Configuration', + } + )} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.DeploymentStep.Configuration.description', + { + defaultMessage: 'Now configure your Elastic crawler and sync the data.', + } + )} +

+
+ + setCurrentStep('configure')} + fill + disabled={!isNextStepEnabled} + > + {Constants.NEXT_BUTTON_LABEL} + +
+
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/finish_up_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/finish_up_step.tsx new file mode 100644 index 0000000000000..28d5387ae4b70 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/finish_up_step.tsx @@ -0,0 +1,348 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; + +import { css } from '@emotion/react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiTitle, + useEuiTheme, + EuiProgress, + EuiText, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +import { APPLICATIONS_PLUGIN } from '../../../../../../common/constants'; + +import { KibanaDeps } from '../../../../../../common/types'; + +import { PLAYGROUND_PATH } from '../../../../applications/routes'; +import { generateEncodedPath } from '../../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../../shared/kibana'; + +import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes'; +import { ConnectorDetailTabId } from '../../connector_detail/connector_detail'; +import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; +import { IndexViewLogic } from '../../search_index/index_view_logic'; +import { SyncsLogic } from '../../shared/header_actions/syncs_logic'; + +import connectorLogo from './assets/connector_logo.svg'; + +interface FinishUpStepProps { + title: string; +} + +export const FinishUpStep: React.FC = ({ title }) => { + const { euiTheme } = useEuiTheme(); + const { + services: { discover }, + } = useKibana(); + const [showNext, setShowNext] = useState(false); + + const { isWaitingForSync, isSyncing: isSyncingProp } = useValues(IndexViewLogic); + const { connector } = useValues(ConnectorViewLogic); + const { startSync } = useActions(SyncsLogic); + + const isSyncing = isWaitingForSync || isSyncingProp; + useEffect(() => { + setTimeout(() => { + window.scrollTo({ + behavior: 'smooth', + top: 0, + }); + }, 100); + }, []); + return ( + <> + + + + +

{title}

+
+ + {isSyncing && ( + <> + + + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.syncingDataTextLabel', + { + defaultMessage: 'Syncing data', + } + )} + + + + + { + setShowNext(true); + }} + /> + + + )} + + + + } + titleSize="s" + title={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.chatWithYourDataLabel', + { defaultMessage: 'Chat with your data' } + )} + description={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.chatWithYourDataDescriptionl', + { + defaultMessage: + 'Combine your data with the power of LLMs for retrieval augmented generation (RAG)', + } + )} + footer={ + showNext ? ( + { + if (connector) { + KibanaLogic.values.navigateToUrl( + `${APPLICATIONS_PLUGIN.URL}${PLAYGROUND_PATH}?default-index=${connector.index_name}`, + { shouldNotCreateHref: true } + ); + } + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.startSearchPlaygroundButtonLabel', + { defaultMessage: 'Start Search Playground' } + )} + + ) : ( + { + startSync(connector); + setShowNext(true); + }} + > + {isSyncing ? 'Syncing data' : 'First sync data'} + + ) + } + /> + + + } + titleSize="s" + title={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.exploreYourDataLabel', + { defaultMessage: 'Explore your data' } + )} + description={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.exploreYourDataDescription', + { + defaultMessage: + 'See your connector documents or make a data view to explore them', + } + )} + footer={ + showNext ? ( + { + discover.locator?.navigate({ + dataViewSpec: { + title: connector?.name, + }, + indexPattern: connector?.index_name, + title: connector?.name, + }); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.viewInDiscoverButtonLabel', + { defaultMessage: 'View in Discover' } + )} + + ) : ( + { + startSync(connector); + setShowNext(true); + }} + > + {isSyncing ? 'Syncing data' : 'First sync data'} + + ) + } + /> + + + } + titleSize="s" + title={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.manageYourConnectorLabel', + { defaultMessage: 'Manage your connector' } + )} + description={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.manageYourConnectorDescription', + { + defaultMessage: + 'Now you can manage your connector, schedule a sync and much more', + } + )} + footer={ + + + { + if (connector) { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId: connector.id, + tabId: ConnectorDetailTabId.CONFIGURATION, + }) + ); + } + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.manageConnectorButtonLabel', + { defaultMessage: 'Manage connector' } + )} + + + + } + /> + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.h3.queryYourDataLabel', + { + defaultMessage: 'Query your data', + } + )} +

+
+ + + + css` + margin-top: ${euiTheme.size.xs}; + `} + size="m" + type="visVega" + /> + } + title={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.queryWithLanguageClientsLabel', + { defaultMessage: 'Query with language clients' } + )} + titleSize="xs" + description={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.queryWithLanguageClientsLDescription', + { + defaultMessage: + 'Use your favorite language client to query your data in your app', + } + )} + onClick={() => {}} + display="subdued" + /> + + + css` + margin-top: ${euiTheme.size.xs}; + `} + size="m" + type="console" + /> + } + title={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.devToolsLabel', + { defaultMessage: 'Dev tools' } + )} + titleSize="xs" + description={i18n.translate( + 'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.devToolsDescription', + { + defaultMessage: + 'Tools for interacting with your data, such as console, profiler, Grok debugger and more', + } + )} + onClick={() => {}} + display="subdued" + /> + + +
+
+
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/index.ts new file mode 100644 index 0000000000000..f3992cbcf9fc9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { CreateConnector } from './create_connector'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx new file mode 100644 index 0000000000000..633ea8f58d25c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx @@ -0,0 +1,340 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ChangeEvent } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiPanel, + EuiRadio, + EuiSpacer, + EuiText, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import * as Constants from '../../../../shared/constants'; +import { GeneratedConfigFields } from '../../connector_detail/components/generated_config_fields'; + +import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; +import { NewConnectorLogic } from '../../new_index/method_connector/new_connector_logic'; + +import { ChooseConnectorSelectable } from './components/choose_connector_selectable'; +import { ConnectorDescriptionPopover } from './components/connector_description_popover'; +import { ManualConfiguration } from './components/manual_configuration'; +import { SelfManagePreference } from './create_connector'; + +interface StartStepProps { + error?: string | React.ReactNode; + onSelfManagePreferenceChange(preference: SelfManagePreference): void; + selfManagePreference: SelfManagePreference; + setCurrentStep: Function; + title: string; +} + +export const StartStep: React.FC = ({ + title, + selfManagePreference, + setCurrentStep, + onSelfManagePreferenceChange, + error, +}) => { + const elasticManagedRadioButtonId = useGeneratedHtmlId({ prefix: 'elasticManagedRadioButton' }); + const selfManagedRadioButtonId = useGeneratedHtmlId({ prefix: 'selfManagedRadioButton' }); + + const { + rawName, + canConfigureConnector, + selectedConnector, + generatedConfigData, + isGenerateLoading, + isCreateLoading, + } = useValues(NewConnectorLogic); + const { setRawName, createConnector, generateConnectorName } = useActions(NewConnectorLogic); + const { connector } = useValues(ConnectorViewLogic); + + const handleNameChange = (e: ChangeEvent) => { + setRawName(e.target.value); + }; + + return ( + + + + + +

{title}

+
+ + + + + + + + + + { + if (selectedConnector) { + generateConnectorName({ + connectorName: rawName, + connectorType: selectedConnector.serviceType, + }); + } + }} + /> + + + + + + + + + +
+
+ {/* Set up */} + + + +

+ {i18n.translate('xpack.enterpriseSearch.createConnector.startStep.h4.setUpLabel', { + defaultMessage: 'Set up', + })} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.p.whereDoYouWantLabel', + { + defaultMessage: + 'Where do you want to store the connector and how do you want to manage it?', + } + )} +

+
+ + + + onSelfManagePreferenceChange('native')} + name="setUp" + /> + + + + +     + + onSelfManagePreferenceChange('selfManaged')} + name="setUp" + /> + + + + + +
+
+ {selfManagePreference === 'selfManaged' ? ( + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.h4.deploymentLabel', + { + defaultMessage: 'Deployment', + } + )} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.p.youWillStartTheLabel', + { + defaultMessage: + 'You will start the process of creating a new index, API key, and a Web Crawler Connector ID manually. Optionally you can bring your own configuration as well.', + } + )} +

+
+ + { + if (selectedConnector && selectedConnector.name) { + createConnector({ + isSelfManaged: true, + }); + setCurrentStep('deployment'); + } + }} + fill + disabled={!canConfigureConnector} + isLoading={isCreateLoading || isGenerateLoading} + > + {Constants.NEXT_BUTTON_LABEL} + +
+
+ ) : ( + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.h4.configureIndexAndAPILabel', + { + defaultMessage: 'Configure index and API key', + } + )} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.p.thisProcessWillCreateLabel', + { + defaultMessage: + 'This process will create a new index, API key, and a Connector ID. Optionally you can bring your own configuration as well.', + } + )} +

+
+ + {generatedConfigData && connector ? ( + <> + + + setCurrentStep('configure')} + > + {Constants.NEXT_BUTTON_LABEL} + + + ) : ( + + + { + createConnector({ + isSelfManaged: false, + }); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.button.label', + { + defaultMessage: 'Generate configuration', + } + )} + + + + + + + )} +
+
+ )} +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/utils/generate_step_state.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/utils/generate_step_state.ts new file mode 100644 index 0000000000000..329ab69b5550f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/utils/generate_step_state.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 { EuiStepStatus } from '@elastic/eui'; + +type Steps = 'start' | 'configure' | 'deployment' | 'finish'; + +export const generateStepState = (currentStep: Steps): { [key in Steps]: EuiStepStatus } => { + return { + configure: + currentStep === 'start' || currentStep === 'deployment' + ? 'incomplete' + : currentStep === 'configure' + ? 'current' + : 'complete', + deployment: + currentStep === 'deployment' + ? 'current' + : currentStep === 'finish' || currentStep === 'configure' + ? 'complete' + : 'incomplete', + finish: currentStep === 'finish' ? 'current' : 'incomplete', + start: currentStep === 'start' ? 'current' : 'complete', + }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts index 3eeb8f306dc2f..da2dcb1198800 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts @@ -7,65 +7,214 @@ import { kea, MakeLogicType } from 'kea'; +import { Connector } from '@kbn/search-connectors'; +import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public'; + +import { Status } from '../../../../../../common/types/api'; import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { generateEncodedPath } from '../../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../../shared/kibana'; import { AddConnectorApiLogic, + AddConnectorApiLogicActions, AddConnectorApiLogicArgs, AddConnectorApiLogicResponse, } from '../../../api/connector/add_connector_api_logic'; import { - IndexExistsApiLogic, - IndexExistsApiParams, - IndexExistsApiResponse, -} from '../../../api/index/index_exists_api_logic'; - -import { isValidIndexName } from '../../../utils/validate_index_name'; + GenerateConfigApiActions, + GenerateConfigApiLogic, +} from '../../../api/connector/generate_connector_config_api_logic'; +import { + GenerateConnectorNamesApiLogic, + GenerateConnectorNamesApiLogicActions, + GenerateConnectorNamesApiResponse, +} from '../../../api/connector/generate_connector_names_api_logic'; +import { APIKeyResponse } from '../../../api/generate_api_key/generate_api_key_logic'; -import { UNIVERSAL_LANGUAGE_VALUE } from '../constants'; -import { LanguageForOptimization } from '../types'; -import { getLanguageForOptimization } from '../utils'; +import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes'; +import { + ConnectorViewActions, + ConnectorViewLogic, +} from '../../connector_detail/connector_view_logic'; +import { ConnectorCreationSteps } from '../../connectors/create_connector/create_connector'; +import { SearchIndexTabId } from '../../search_index/search_index'; export interface NewConnectorValues { - data: IndexExistsApiResponse; - fullIndexName: string; - fullIndexNameExists: boolean; - fullIndexNameIsValid: boolean; - language: LanguageForOptimization; - languageSelectValue: string; + canConfigureConnector: boolean; + connectorId: string; + connectorName: string; + createConnectorApiStatus: Status; + currentStep: ConnectorCreationSteps; + generateConfigurationStatus: Status; + generatedConfigData: + | { + apiKey: APIKeyResponse['apiKey']; + connectorId: Connector['id']; + indexName: string; + } + | undefined; + generatedNameData: GenerateConnectorNamesApiResponse | undefined; + isCreateLoading: boolean; + isGenerateLoading: boolean; rawName: string; + selectedConnector: ConnectorDefinition | null; + shouldGenerateConfigAfterCreate: boolean; } -type NewConnectorActions = Pick< - Actions, - 'makeRequest' -> & { +type NewConnectorActions = { + generateConnectorName: GenerateConnectorNamesApiLogicActions['makeRequest']; +} & { + configurationGenerated: GenerateConfigApiActions['apiSuccess']; + generateConfiguration: GenerateConfigApiActions['makeRequest']; +} & { connectorCreated: Actions['apiSuccess']; - setLanguageSelectValue(language: string): { language: string }; + createConnector: ({ + isSelfManaged, + shouldGenerateAfterCreate, + shouldNavigateToConnectorAfterCreate, + }: { + isSelfManaged: boolean; + shouldGenerateAfterCreate?: boolean; + shouldNavigateToConnectorAfterCreate?: boolean; + }) => { + isSelfManaged: boolean; + shouldGenerateAfterCreate?: boolean; + shouldNavigateToConnectorAfterCreate?: boolean; + }; + createConnectorApi: AddConnectorApiLogicActions['makeRequest']; + fetchConnector: ConnectorViewActions['fetchConnector']; + setCurrentStep(step: ConnectorCreationSteps): { step: ConnectorCreationSteps }; setRawName(rawName: string): { rawName: string }; + setSelectedConnector(connector: ConnectorDefinition | null): { + connector: ConnectorDefinition | null; + }; }; export const NewConnectorLogic = kea>({ actions: { - setLanguageSelectValue: (language) => ({ language }), + createConnector: ({ + isSelfManaged, + shouldGenerateAfterCreate, + shouldNavigateToConnectorAfterCreate, + }) => ({ + isSelfManaged, + shouldGenerateAfterCreate, + shouldNavigateToConnectorAfterCreate, + }), + setCurrentStep: (step) => ({ step }), setRawName: (rawName) => ({ rawName }), + setSelectedConnector: (connector) => ({ connector }), }, connect: { actions: [ + GenerateConnectorNamesApiLogic, + ['makeRequest as generateConnectorName', 'apiSuccess as connectorNameGenerated'], AddConnectorApiLogic, - ['apiSuccess as connectorCreated'], - IndexExistsApiLogic, - ['makeRequest'], + ['makeRequest as createConnectorApi', 'apiSuccess as connectorCreated'], + GenerateConfigApiLogic, + ['makeRequest as generateConfiguration', 'apiSuccess as configurationGenerated'], + ConnectorViewLogic, + ['fetchConnector'], + ], + values: [ + GenerateConnectorNamesApiLogic, + ['data as generatedNameData'], + GenerateConfigApiLogic, + ['data as generatedConfigData', 'status as generateConfigurationStatus'], + AddConnectorApiLogic, + ['status as createConnectorApiStatus'], ], - values: [IndexExistsApiLogic, ['data']], }, - path: ['enterprise_search', 'content', 'new_search_index'], + listeners: ({ actions, values }) => ({ + connectorCreated: ({ id, uiFlags }) => { + if (uiFlags?.shouldNavigateToConnectorAfterCreate) { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId: id, + tabId: SearchIndexTabId.CONFIGURATION, + }) + ); + } else { + actions.fetchConnector({ connectorId: id }); + if (!uiFlags || uiFlags.shouldGenerateAfterCreate) { + actions.generateConfiguration({ connectorId: id }); + } + } + }, + connectorNameGenerated: ({ connectorName }) => { + if (!values.rawName) { + actions.setRawName(connectorName); + } + }, + createConnector: ({ + isSelfManaged, + shouldGenerateAfterCreate = true, + shouldNavigateToConnectorAfterCreate = false, + }) => { + if ( + !values.rawName && + values.selectedConnector && + values.connectorName && + values.generatedNameData + ) { + // name is generated, use everything generated + actions.createConnectorApi({ + deleteExistingConnector: false, + indexName: values.connectorName, + isNative: !values.selectedConnector.isNative ? false : !isSelfManaged, + language: null, + name: values.generatedNameData.connectorName, + serviceType: values.selectedConnector.serviceType, + uiFlags: { + shouldGenerateAfterCreate, + shouldNavigateToConnectorAfterCreate, + }, + }); + } else { + if (values.generatedNameData && values.selectedConnector) { + actions.createConnectorApi({ + deleteExistingConnector: false, + indexName: values.generatedNameData.indexName, + isNative: !values.selectedConnector.isNative ? false : !isSelfManaged, + language: null, + name: values.connectorName, + serviceType: values.selectedConnector?.serviceType, + uiFlags: { + shouldGenerateAfterCreate, + shouldNavigateToConnectorAfterCreate, + }, + }); + } + } + }, + setSelectedConnector: ({ connector }) => { + if (connector) { + actions.generateConnectorName({ + connectorName: values.rawName, + connectorType: connector.serviceType, + }); + } + }, + }), + path: ['enterprise_search', 'content', 'new_search_connector'], reducers: { - languageSelectValue: [ - UNIVERSAL_LANGUAGE_VALUE, + connectorId: [ + '', { - // @ts-expect-error upgrade typescript v5.1.6 - setLanguageSelectValue: (_, { language }) => language ?? null, + connectorCreated: ( + _: NewConnectorValues['connectorId'], + { id }: { id: NewConnectorValues['connectorId'] } + ) => id, + }, + ], + currentStep: [ + 'start', + { + setCurrentStep: ( + _: NewConnectorValues['currentStep'], + { step }: { step: NewConnectorValues['currentStep'] } + ) => step, }, ], rawName: [ @@ -75,21 +224,34 @@ export const NewConnectorLogic = kea rawName, }, ], + selectedConnector: [ + null, + { + setSelectedConnector: ( + _: NewConnectorValues['selectedConnector'], + { connector }: { connector: NewConnectorValues['selectedConnector'] } + ) => connector, + }, + ], }, selectors: ({ selectors }) => ({ - fullIndexName: [() => [selectors.rawName], (name: string) => name], - fullIndexNameExists: [ - () => [selectors.data, selectors.fullIndexName], - (data: IndexExistsApiResponse | undefined, fullIndexName: string) => - data?.exists === true && data.indexName === fullIndexName, + canConfigureConnector: [ + () => [selectors.connectorName, selectors.selectedConnector], + (connectorName: string, selectedConnector: NewConnectorValues['selectedConnector']) => + (connectorName && selectedConnector?.name) ?? false, + ], + connectorName: [ + () => [selectors.rawName, selectors.generatedNameData], + (name: string, generatedName: NewConnectorValues['generatedNameData']) => + name ? name : generatedName?.connectorName ?? '', ], - fullIndexNameIsValid: [ - () => [selectors.fullIndexName], - (fullIndexName) => isValidIndexName(fullIndexName), + isCreateLoading: [ + () => [selectors.createConnectorApiStatus], + (status) => status === Status.LOADING, ], - language: [ - () => [selectors.languageSelectValue], - (languageSelectValue) => getLanguageForOptimization(languageSelectValue), + isGenerateLoading: [ + () => [selectors.generateConfigurationStatus], + (status) => status === Status.LOADING, ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_template.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_template.tsx index 4b4aba1761450..773c81761944d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_template.tsx @@ -54,44 +54,17 @@ export const NewConnectorTemplate: React.FC = ({ type, isBeta, }) => { - const { fullIndexName, fullIndexNameExists, fullIndexNameIsValid, rawName } = - useValues(NewConnectorLogic); + const { connectorName, rawName } = useValues(NewConnectorLogic); const { setRawName } = useActions(NewConnectorLogic); const handleNameChange = (e: ChangeEvent) => { setRawName(e.target.value); if (onNameChange) { - onNameChange(fullIndexName); + onNameChange(connectorName); } }; - const formInvalid = !!error || fullIndexNameExists || !fullIndexNameIsValid; - - const formError = () => { - if (fullIndexNameExists) { - return i18n.translate( - 'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error', - { - defaultMessage: 'A connector with the name {connectorName} already exists', - values: { - connectorName: fullIndexName, - }, - } - ); - } - if (!fullIndexNameIsValid) { - return i18n.translate( - 'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error', - { - defaultMessage: '{connectorName} is an invalid connector name', - values: { - connectorName: fullIndexName, - }, - } - ); - } - return error; - }; + const formInvalid = !!error; return ( <> @@ -100,7 +73,7 @@ export const NewConnectorTemplate: React.FC = ({ id="enterprise-search-create-connector" onSubmit={(event) => { event.preventDefault(); - onSubmit(fullIndexName); + onSubmit(connectorName); }} > @@ -131,10 +104,10 @@ export const NewConnectorTemplate: React.FC = ({ } )} isInvalid={formInvalid} - error={formError()} fullWidth > = ({ {type === INGESTION_METHOD_IDS.CONNECTOR && ( - + {i18n.translate( 'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText', { @@ -182,6 +159,7 @@ export const NewConnectorTemplate: React.FC = ({ history.back()} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index 6be30af4e986b..092b60bf7666f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -21,6 +21,7 @@ export const NEW_ES_INDEX_PATH = `${NEW_INDEX_PATH}/elasticsearch`; export const NEW_DIRECT_UPLOAD_PATH = `${NEW_INDEX_PATH}/upload`; export const NEW_INDEX_SELECT_CONNECTOR_PATH = `${CONNECTORS_PATH}/select_connector`; export const NEW_CONNECTOR_PATH = `${CONNECTORS_PATH}/new_connector`; +export const NEW_CONNECTOR_FLOW_PATH = `${CONNECTORS_PATH}/new_connector_flow`; export const NEW_CRAWLER_PATH = `${CRAWLERS_PATH}/new_crawler`; export const NEW_INDEX_SELECT_CONNECTOR_NATIVE_PATH = `${CONNECTORS_PATH}/select_connector?filter=native`; export const NEW_INDEX_SELECT_CONNECTOR_CLIENTS_PATH = `${CONNECTORS_PATH}/select_connector?filter=connector_clients`; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/actions.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/actions.ts index f163158462f0d..fc9860e202130 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/constants/actions.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/actions.ts @@ -49,6 +49,10 @@ export const BACK_BUTTON_LABEL = i18n.translate('xpack.enterpriseSearch.actions. defaultMessage: 'Back', }); +export const NEXT_BUTTON_LABEL = i18n.translate('xpack.enterpriseSearch.actions.nextButtonLabel', { + defaultMessage: 'Next', +}); + export const CLOSE_BUTTON_LABEL = i18n.translate( 'xpack.enterpriseSearch.actions.closeButtonLabel', { defaultMessage: 'Close' } diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/generate_connector_name.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/generate_connector_name.ts index f6c209707a8f7..56f849c551400 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/generate_connector_name.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/generate_connector_name.ts @@ -16,24 +16,51 @@ import { indexOrAliasExists } from '../indices/exists_index'; export const generateConnectorName = async ( client: IScopedClusterClient, - connectorType: string + connectorType: string, + userConnectorName?: string ): Promise<{ apiKeyName: string; connectorName: string; indexName: string }> => { const prefix = toAlphanumeric(connectorType); if (!prefix || prefix.length === 0) { - throw new Error('Connector type is required'); + throw new Error('Connector type or connectorName is required'); } - for (let i = 0; i < 20; i++) { - const connectorName = `${prefix}-${uuidv4().split('-')[1]}`; - const indexName = `connector-${connectorName}`; - - const result = await indexOrAliasExists(client, indexName); - if (!result) { + if (userConnectorName) { + let indexName = `connector-${userConnectorName}`; + const resultSameName = await indexOrAliasExists(client, indexName); + // index with same name doesn't exist + if (!resultSameName) { return { - apiKeyName: indexName, - connectorName, + apiKeyName: userConnectorName, + connectorName: userConnectorName, indexName, }; } + // if the index name already exists, we will generate until it doesn't for 20 times + for (let i = 0; i < 20; i++) { + indexName = `connector-${userConnectorName}-${uuidv4().split('-')[1].slice(0, 4)}`; + + const result = await indexOrAliasExists(client, indexName); + if (!result) { + return { + apiKeyName: indexName, + connectorName: userConnectorName, + indexName, + }; + } + } + } else { + for (let i = 0; i < 20; i++) { + const connectorName = `${prefix}-${uuidv4().split('-')[1].slice(0, 4)}`; + const indexName = `connector-${connectorName}`; + + const result = await indexOrAliasExists(client, indexName); + if (!result) { + return { + apiKeyName: indexName, + connectorName, + indexName, + }; + } + } } throw new Error(ErrorCode.GENERATE_INDEX_NAME_ERROR); }; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 21b00e82b6aa0..6108580463893 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -6,7 +6,6 @@ */ import { schema } from '@kbn/config-schema'; - import { ElasticsearchErrorDetails } from '@kbn/es-errors'; import { i18n } from '@kbn/i18n'; @@ -841,15 +840,20 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { path: '/internal/enterprise_search/connectors/generate_connector_name', validate: { body: schema.object({ + connectorName: schema.maybe(schema.string()), connectorType: schema.string(), }), }, }, elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const { connectorType } = request.body; + const { connectorType, connectorName } = request.body; try { - const generatedNames = await generateConnectorName(client, connectorType ?? 'custom'); + const generatedNames = await generateConnectorName( + client, + connectorType ?? 'custom', + connectorName + ); return response.ok({ body: generatedNames, headers: { 'content-type': 'application/json' }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index af71b7b1b9eda..c9713d7d10c73 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -16858,10 +16858,8 @@ "xpack.enterpriseSearch.content.new_index.genericTitle": "Nouvel index de recherche", "xpack.enterpriseSearch.content.new_index.successToast.title": "L’index a bien été créé", "xpack.enterpriseSearch.content.new_web_crawler.breadcrumbs": "Nouveau robot d'indexation", - "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error": "Un connecteur nommé {connectorName} existe déjà", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.createIndex.buttonText": "Créer un connecteur", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.formTitle": "Créer un connecteur", - "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error": "{connectorName} est un nom de connecteur non valide", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText": "En savoir plus sur les connecteurs", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputHelpText.lineTwo": "Les noms doivent être en minuscules et ne peuvent pas contenir d'espaces ni de caractères spéciaux.", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputLabel": "Nom du connecteur", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cdd8afc68af2e..d123d0edd8948 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -16604,10 +16604,8 @@ "xpack.enterpriseSearch.content.new_index.genericTitle": "新しい検索インデックス", "xpack.enterpriseSearch.content.new_index.successToast.title": "インデックスが正常に作成されました", "xpack.enterpriseSearch.content.new_web_crawler.breadcrumbs": "新しいWebクローラー", - "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error": "名前\"{connectorName}\"のコネクターはすでに存在しています", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.createIndex.buttonText": "コネクターを作成", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.formTitle": "コネクターを作成する", - "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error": "{connectorName}は無効なコネクター名です", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText": "コネクターの詳細", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputHelpText.lineTwo": "名前は小文字で入力してください。スペースや特殊文字は使用できません。", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputLabel": "コネクター名", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b94fb455c8ad5..3e658947b010b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16633,10 +16633,8 @@ "xpack.enterpriseSearch.content.new_index.genericTitle": "新搜索索引", "xpack.enterpriseSearch.content.new_index.successToast.title": "已成功创建索引", "xpack.enterpriseSearch.content.new_web_crawler.breadcrumbs": "新网络爬虫", - "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error": "名为 {connectorName} 的连接器已存在", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.createIndex.buttonText": "创建连接器", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.formTitle": "创建连接器", - "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error": "{connectorName} 为无效的连接器名称", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText": "详细了解连接器", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputHelpText.lineTwo": "名称应为小写,并且不能包含空格或特殊字符。", "xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputLabel": "连接器名称", From 8cceaee0f42c6c0e7ee064ef98a0e652fd77e286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Tue, 15 Oct 2024 13:18:30 +0100 Subject: [PATCH 20/84] [Stateful sidenav] Welcome tour (#194926) --- x-pack/plugins/spaces/common/constants.ts | 5 + .../components/spaces_description.tsx | 4 +- .../nav_control/components/spaces_menu.tsx | 3 +- .../spaces/public/nav_control/nav_control.tsx | 5 + .../nav_control/nav_control_popover.test.tsx | 73 +++++++++- .../nav_control/nav_control_popover.tsx | 63 +++++++-- .../nav_control/solution_view_tour/index.ts | 10 ++ .../nav_control/solution_view_tour/lib.ts | 84 +++++++++++ .../solution_view_tour/solution_view_tour.tsx | 94 +++++++++++++ x-pack/plugins/spaces/server/plugin.ts | 2 + x-pack/plugins/spaces/server/ui_settings.ts | 24 ++++ x-pack/test/common/services/spaces.ts | 33 +++++ .../solution_view_flag_enabled/index.ts | 1 + .../solution_tour.ts | 133 ++++++++++++++++++ 14 files changed, 509 insertions(+), 25 deletions(-) create mode 100644 x-pack/plugins/spaces/public/nav_control/solution_view_tour/index.ts create mode 100644 x-pack/plugins/spaces/public/nav_control/solution_view_tour/lib.ts create mode 100644 x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx create mode 100644 x-pack/plugins/spaces/server/ui_settings.ts create mode 100644 x-pack/test/functional/apps/spaces/solution_view_flag_enabled/solution_tour.ts diff --git a/x-pack/plugins/spaces/common/constants.ts b/x-pack/plugins/spaces/common/constants.ts index 14932a93a06b7..232892ab7b9ad 100644 --- a/x-pack/plugins/spaces/common/constants.ts +++ b/x-pack/plugins/spaces/common/constants.ts @@ -52,3 +52,8 @@ export const API_VERSIONS = { v1: '2023-10-31', }, }; + +/** + * The setting to control whether the Space Solution Tour is shown. + */ +export const SHOW_SPACE_SOLUTION_TOUR_SETTING = 'showSpaceSolutionTour'; diff --git a/x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx b/x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx index 982e11ffbf4e7..03667f48f4166 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx @@ -20,7 +20,7 @@ import { getSpacesFeatureDescription } from '../../constants'; interface Props { id: string; isLoading: boolean; - toggleSpaceSelector: () => void; + onClickManageSpaceBtn: () => void; capabilities: Capabilities; navigateToApp: ApplicationStart['navigateToApp']; } @@ -45,7 +45,7 @@ export const SpacesDescription: FC = (props: Props) => { diff --git a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx index 47f7d840b9bee..29d360fe91f3f 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx @@ -43,6 +43,7 @@ interface Props { spaces: Space[]; serverBasePath: string; toggleSpaceSelector: () => void; + onClickManageSpaceBtn: () => void; intl: InjectedIntl; capabilities: Capabilities; navigateToApp: ApplicationStart['navigateToApp']; @@ -218,7 +219,7 @@ class SpacesMenuUI extends Component { key="manageSpacesButton" className="spcMenu__manageButton" size="s" - onClick={this.props.toggleSpaceSelector} + onClick={this.props.onClickManageSpaceBtn} capabilities={this.props.capabilities} navigateToApp={this.props.navigateToApp} /> diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx index 7cb32fff01e1e..1dc888333fdf5 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx @@ -12,6 +12,7 @@ import ReactDOM from 'react-dom'; import type { CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { initTour } from './solution_view_tour'; import type { EventTracker } from '../analytics'; import type { ConfigType } from '../config'; import type { SpacesManager } from '../spaces_manager'; @@ -22,6 +23,8 @@ export function initSpacesNavControl( config: ConfigType, eventTracker: EventTracker ) { + const { showTour$, onFinishTour } = initTour(core, spacesManager); + core.chrome.navControls.registerLeft({ order: 1000, mount(targetDomElement: HTMLElement) { @@ -47,6 +50,8 @@ export function initSpacesNavControl( navigateToUrl={core.application.navigateToUrl} allowSolutionVisibility={config.allowSolutionVisibility} eventTracker={eventTracker} + showTour$={showTour$} + onFinishTour={onFinishTour} /> , diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index 9b573615c65b9..f1ba5c9f3f3cf 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -8,7 +8,6 @@ import { EuiFieldSearch, EuiHeaderSectionItemButton, - EuiPopover, EuiSelectable, EuiSelectableListItem, } from '@elastic/eui'; @@ -18,7 +17,7 @@ import * as Rx from 'rxjs'; import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; -import { NavControlPopover } from './nav_control_popover'; +import { NavControlPopover, type Props as NavControlPopoverProps } from './nav_control_popover'; import type { Space } from '../../common'; import { EventTracker } from '../analytics'; import { SpaceAvatarInternal } from '../space_avatar/space_avatar_internal'; @@ -49,7 +48,12 @@ const reportEvent = jest.fn(); const eventTracker = new EventTracker({ reportEvent }); describe('NavControlPopover', () => { - async function setup(spaces: Space[], allowSolutionVisibility = false, activeSpace?: Space) { + async function setup( + spaces: Space[], + allowSolutionVisibility = false, + activeSpace?: Space, + props?: Partial + ) { const spacesManager = spacesManagerMock.create(); spacesManager.getSpaces = jest.fn().mockResolvedValue(spaces); @@ -68,6 +72,9 @@ describe('NavControlPopover', () => { navigateToUrl={jest.fn()} allowSolutionVisibility={allowSolutionVisibility} eventTracker={eventTracker} + showTour$={Rx.of(false)} + onFinishTour={jest.fn()} + {...props} /> ); @@ -81,7 +88,7 @@ describe('NavControlPopover', () => { it('renders without crashing', () => { const spacesManager = spacesManagerMock.create(); - const { baseElement } = render( + const { baseElement, queryByTestId } = render( { navigateToUrl={jest.fn()} allowSolutionVisibility={false} eventTracker={eventTracker} + showTour$={Rx.of(false)} + onFinishTour={jest.fn()} /> ); expect(baseElement).toMatchSnapshot(); + expect(queryByTestId('spaceSolutionTour')).toBeNull(); }); it('renders a SpaceAvatar with the active space', async () => { @@ -117,6 +127,8 @@ describe('NavControlPopover', () => { navigateToUrl={jest.fn()} allowSolutionVisibility={false} eventTracker={eventTracker} + showTour$={Rx.of(false)} + onFinishTour={jest.fn()} /> ); @@ -223,20 +235,29 @@ describe('NavControlPopover', () => { }); it('can close its popover', async () => { + jest.useFakeTimers(); const wrapper = await setup(mockSpaces); + expect(findTestSubject(wrapper, 'spaceMenuPopoverPanel').exists()).toEqual(false); // closed + + // Open the popover await act(async () => { wrapper.find(EuiHeaderSectionItemButton).find('button').simulate('click'); }); wrapper.update(); - expect(wrapper.find(EuiPopover).props().isOpen).toEqual(true); + expect(findTestSubject(wrapper, 'spaceMenuPopoverPanel').exists()).toEqual(true); // open + // Close the popover await act(async () => { - wrapper.find(EuiPopover).props().closePopover(); + wrapper.find(EuiHeaderSectionItemButton).find('button').simulate('click'); + }); + act(() => { + jest.runAllTimers(); }); wrapper.update(); + expect(findTestSubject(wrapper, 'spaceMenuPopoverPanel').exists()).toEqual(false); // closed - expect(wrapper.find(EuiPopover).props().isOpen).toEqual(false); + jest.useRealTimers(); }); it('should render solution for spaces', async () => { @@ -301,4 +322,42 @@ describe('NavControlPopover', () => { space_id_prev: 'space-1', }); }); + + it('should show the solution view tour', async () => { + jest.useFakeTimers(); // the underlying EUI tour component has a timeout that needs to be flushed for the test to pass + + const spaces: Space[] = [ + { + id: 'space-1', + name: 'Space-1', + disabledFeatures: [], + solution: 'es', + }, + ]; + + const activeSpace = spaces[0]; + const showTour$ = new Rx.BehaviorSubject(true); + const onFinishTour = jest.fn().mockImplementation(() => { + showTour$.next(false); + }); + + const wrapper = await setup(spaces, true /** allowSolutionVisibility **/, activeSpace, { + showTour$, + onFinishTour, + }); + + expect(findTestSubject(wrapper, 'spaceSolutionTour').exists()).toBe(true); + + act(() => { + findTestSubject(wrapper, 'closeTourBtn').simulate('click'); + }); + act(() => { + jest.runAllTimers(); + }); + wrapper.update(); + + expect(findTestSubject(wrapper, 'spaceSolutionTour').exists()).toBe(false); + + jest.useRealTimers(); + }); }); diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx index b9830b2063dd5..d84fac2fdced4 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -13,13 +13,14 @@ import { withEuiTheme, } from '@elastic/eui'; import React, { Component, lazy, Suspense } from 'react'; -import type { Subscription } from 'rxjs'; +import type { Observable, Subscription } from 'rxjs'; import type { ApplicationStart, Capabilities } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { SpacesDescription } from './components/spaces_description'; import { SpacesMenu } from './components/spaces_menu'; +import { SolutionViewTour } from './solution_view_tour'; import type { Space } from '../../common'; import type { EventTracker } from '../analytics'; import { getSpaceAvatarComponent } from '../space_avatar'; @@ -30,7 +31,7 @@ const LazySpaceAvatar = lazy(() => getSpaceAvatarComponent().then((component) => ({ default: component })) ); -interface Props { +export interface Props { spacesManager: SpacesManager; anchorPosition: PopoverAnchorPosition; capabilities: Capabilities; @@ -40,6 +41,8 @@ interface Props { theme: WithEuiThemeProps['theme']; allowSolutionVisibility: boolean; eventTracker: EventTracker; + showTour$: Observable; + onFinishTour: () => void; } interface State { @@ -47,12 +50,14 @@ interface State { loading: boolean; activeSpace: Space | null; spaces: Space[]; + showTour: boolean; } const popoutContentId = 'headerSpacesMenuContent'; class NavControlPopoverUI extends Component { private activeSpace$?: Subscription; + private showTour$Sub?: Subscription; constructor(props: Props) { super(props); @@ -61,6 +66,7 @@ class NavControlPopoverUI extends Component { loading: false, activeSpace: null, spaces: [], + showTour: false, }; } @@ -72,15 +78,23 @@ class NavControlPopoverUI extends Component { }); }, }); + + this.showTour$Sub = this.props.showTour$.subscribe((showTour) => { + this.setState({ showTour }); + }); } public componentWillUnmount() { this.activeSpace$?.unsubscribe(); + this.showTour$Sub?.unsubscribe(); } public render() { const button = this.getActiveSpaceButton(); const { theme } = this.props; + const { activeSpace } = this.state; + + const isTourOpen = Boolean(activeSpace) && this.state.showTour && !this.state.showSpaceSelector; let element: React.ReactNode; if (this.state.loading || this.state.spaces.length < 2) { @@ -88,9 +102,13 @@ class NavControlPopoverUI extends Component { { + // No need to show the tour anymore, the user is taking action + this.props.onFinishTour(); + this.toggleSpaceSelector(); + }} /> ); } else { @@ -106,24 +124,38 @@ class NavControlPopoverUI extends Component { activeSpace={this.state.activeSpace} allowSolutionVisibility={this.props.allowSolutionVisibility} eventTracker={this.props.eventTracker} + onClickManageSpaceBtn={() => { + // No need to show the tour anymore, the user is taking action + this.props.onFinishTour(); + this.toggleSpaceSelector(); + }} /> ); } return ( - - {element} - + + {element} + + ); } @@ -195,6 +227,7 @@ class NavControlPopoverUI extends Component { protected toggleSpaceSelector = () => { const isOpening = !this.state.showSpaceSelector; + if (isOpening) { this.loadSpaces(); } diff --git a/x-pack/plugins/spaces/public/nav_control/solution_view_tour/index.ts b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/index.ts new file mode 100644 index 0000000000000..d85a76c586925 --- /dev/null +++ b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { initTour } from './lib'; + +export { SolutionViewTour } from './solution_view_tour'; diff --git a/x-pack/plugins/spaces/public/nav_control/solution_view_tour/lib.ts b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/lib.ts new file mode 100644 index 0000000000000..7936eea09dab6 --- /dev/null +++ b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/lib.ts @@ -0,0 +1,84 @@ +/* + * 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 { BehaviorSubject, defer, from, map, of, shareReplay, switchMap } from 'rxjs'; + +import type { CoreStart } from '@kbn/core/public'; + +import type { Space } from '../../../common'; +import { + DEFAULT_SPACE_ID, + SHOW_SPACE_SOLUTION_TOUR_SETTING, + SOLUTION_VIEW_CLASSIC, +} from '../../../common/constants'; +import type { SpacesManager } from '../../spaces_manager'; + +export function initTour(core: CoreStart, spacesManager: SpacesManager) { + const showTourUiSettingValue = core.settings.globalClient.get(SHOW_SPACE_SOLUTION_TOUR_SETTING); + const showTour$ = new BehaviorSubject(showTourUiSettingValue ?? true); + + const allSpaces$ = defer(() => from(spacesManager.getSpaces())).pipe(shareReplay(1)); + + const hasMultipleSpaces = (spaces: Space[]) => { + return spaces.length > 1; + }; + + const isDefaultSpaceOnClassic = (spaces: Space[]) => { + const defaultSpace = spaces.find((space) => space.id === DEFAULT_SPACE_ID); + + if (!defaultSpace) { + // Don't show the tour if the default space doesn't exist (this should never happen) + return true; + } + + if (!defaultSpace.solution || defaultSpace.solution === SOLUTION_VIEW_CLASSIC) { + return true; + } + }; + + const showTourObservable$ = showTour$.pipe( + switchMap((showTour) => { + if (!showTour) return of(false); + + return allSpaces$.pipe( + map((spaces) => { + if (hasMultipleSpaces(spaces) || isDefaultSpaceOnClassic(spaces)) { + return false; + } + + return true; + }) + ); + }) + ); + + const hideTourInGlobalSettings = () => { + core.settings.globalClient.set(SHOW_SPACE_SOLUTION_TOUR_SETTING, false).catch(() => { + // Silently swallow errors, the user will just see the tour again next time they load the page + }); + }; + + if (showTourUiSettingValue !== false) { + allSpaces$.subscribe((spaces) => { + if (hasMultipleSpaces(spaces) || isDefaultSpaceOnClassic(spaces)) { + // If we have either (1) multiple space or (2) only one space and it's the default space with the classic solution, + // we don't want to show the tour later on. This can happen in the following scenarios: + // - the user deletes all the spaces but one (and that last space has a solution set) + // - the user edits the default space and sets a solution + // So we can immediately hide the tour in the global settings from now on. + hideTourInGlobalSettings(); + } + }); + } + + const onFinishTour = () => { + hideTourInGlobalSettings(); + showTour$.next(false); + }; + + return { showTour$: showTourObservable$, onFinishTour }; +} diff --git a/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx new file mode 100644 index 0000000000000..bf80bf92bdf4e --- /dev/null +++ b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonEmpty, EuiLink, EuiText, EuiTourStep } from '@elastic/eui'; +import React from 'react'; +import type { FC, PropsWithChildren } from 'react'; + +import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { SolutionView } from '../../../common'; +import { SOLUTION_VIEW_CLASSIC } from '../../../common/constants'; + +const tourLearnMoreLink = 'https://ela.st/left-nav'; + +const LearnMoreLink = () => ( + + {i18n.translate('xpack.spaces.navControl.tour.learnMore', { + defaultMessage: 'Learn more', + })} + +); + +const solutionMap: Record = { + es: i18n.translate('xpack.spaces.navControl.tour.esSolution', { + defaultMessage: 'Search', + }), + security: i18n.translate('xpack.spaces.navControl.tour.securitySolution', { + defaultMessage: 'Security', + }), + oblt: i18n.translate('xpack.spaces.navControl.tour.obltSolution', { + defaultMessage: 'Observability', + }), +}; + +interface Props extends PropsWithChildren<{}> { + solution?: SolutionView; + isTourOpen: boolean; + onFinishTour: () => void; +} + +export const SolutionViewTour: FC = ({ children, solution, isTourOpen, onFinishTour }) => { + const solutionLabel = solution && solution !== SOLUTION_VIEW_CLASSIC ? solutionMap[solution] : ''; + if (!solutionLabel) { + return children; + } + + return ( + +

+ , + }} + /> +

+ + } + isStepOpen={isTourOpen} + minWidth={300} + maxWidth={360} + onFinish={onFinishTour} + step={1} + stepsTotal={1} + title={i18n.translate('xpack.spaces.navControl.tour.title', { + defaultMessage: 'You chose the {solution} solution view', + values: { solution: solutionLabel }, + })} + anchorPosition="downCenter" + footerAction={ + + {i18n.translate('xpack.spaces.navControl.tour.closeBtn', { + defaultMessage: 'Close', + })} + + } + panelProps={{ + 'data-test-subj': 'spaceSolutionTour', + }} + > + <>{children} +
+ ); +}; diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 2f8fb2ec30842..e36a6fb3cc7f1 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -35,6 +35,7 @@ import { SpacesClientService } from './spaces_client'; import type { SpacesServiceSetup, SpacesServiceStart } from './spaces_service'; import { SpacesService } from './spaces_service'; import type { SpacesRequestHandlerContext } from './types'; +import { getUiSettings } from './ui_settings'; import { registerSpacesUsageCollector } from './usage_collection'; import { UsageStatsService } from './usage_stats'; import { SpacesLicenseService } from '../common/licensing'; @@ -149,6 +150,7 @@ export class SpacesPlugin public setup(core: CoreSetup, plugins: PluginsSetup): SpacesPluginSetup { this.onCloud$.next(plugins.cloud !== undefined && plugins.cloud.isCloudEnabled); const spacesClientSetup = this.spacesClientService.setup({ config$: this.config$ }); + core.uiSettings.registerGlobal(getUiSettings()); const spacesServiceSetup = this.spacesService.setup({ basePath: core.http.basePath, diff --git a/x-pack/plugins/spaces/server/ui_settings.ts b/x-pack/plugins/spaces/server/ui_settings.ts new file mode 100644 index 0000000000000..cfb6c996296da --- /dev/null +++ b/x-pack/plugins/spaces/server/ui_settings.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import type { UiSettingsParams } from '@kbn/core/types'; + +import { SHOW_SPACE_SOLUTION_TOUR_SETTING } from '../common/constants'; + +/** + * uiSettings definitions for Spaces + */ +export const getUiSettings = (): Record => { + return { + [SHOW_SPACE_SOLUTION_TOUR_SETTING]: { + schema: schema.boolean(), + readonly: true, + readonlyMode: 'ui', + }, + }; +}; diff --git a/x-pack/test/common/services/spaces.ts b/x-pack/test/common/services/spaces.ts index 98cc54e456200..67da912fb6a54 100644 --- a/x-pack/test/common/services/spaces.ts +++ b/x-pack/test/common/services/spaces.ts @@ -77,6 +77,25 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) { }; } + public async update( + id: string, + updatedSpace: Partial, + { overwrite = true }: { overwrite?: boolean } = {} + ) { + log.debug(`updating space ${id}`); + const { data, status, statusText } = await axios.put( + `/api/spaces/space/${id}?overwrite=${overwrite}`, + updatedSpace + ); + + if (status !== 200) { + throw new Error( + `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}` + ); + } + log.debug(`updated space ${id}`); + } + public async delete(spaceId: string) { log.debug(`deleting space id: ${spaceId}`); const { data, status, statusText } = await axios.delete(`/api/spaces/space/${spaceId}`); @@ -89,6 +108,20 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) { log.debug(`deleted space id: ${spaceId}`); } + public async get(id: string) { + log.debug(`retrieving space ${id}`); + const { data, status, statusText } = await axios.get(`/api/spaces/space/${id}`); + + if (status !== 200) { + throw new Error( + `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}` + ); + } + log.debug(`retrieved space ${id}`); + + return data; + } + public async getAll() { log.debug('retrieving all spaces'); const { data, status, statusText } = await axios.get('/api/spaces/space'); diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts index 99ce8f2ab16e7..45a8f78387154 100644 --- a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts @@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function spacesApp({ loadTestFile }: FtrProviderContext) { describe('Spaces app (with solution view)', function spacesAppTestSuite() { loadTestFile(require.resolve('./create_edit_space')); + loadTestFile(require.resolve('./solution_tour')); }); } diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/solution_tour.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/solution_tour.ts new file mode 100644 index 0000000000000..852a2a83031cd --- /dev/null +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/solution_tour.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { SolutionView, Space } from '@kbn/spaces-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']); + const testSubjects = getService('testSubjects'); + const spacesService = getService('spaces'); + const browser = getService('browser'); + const es = getService('es'); + const log = getService('log'); + + describe('space solution tour', () => { + let version: string | undefined; + + const removeGlobalSettings = async () => { + version = version ?? (await kibanaServer.version.get()); + version = version.replace(/-SNAPSHOT$/, ''); + + log.debug(`Deleting [config-global:${version}] doc from the .kibana index`); + + await es + .delete( + { id: `config-global:${version}`, index: '.kibana', refresh: true }, + { headers: { 'kbn-xsrf': 'spaces' } } + ) + .catch((error) => { + if (error.statusCode === 404) return; // ignore 404 errors + throw error; + }); + }; + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + describe('solution tour', () => { + let _defaultSpace: Space | undefined = { + id: 'default', + name: 'Default', + disabledFeatures: [], + }; + + const updateSolutionDefaultSpace = async (solution: SolutionView) => { + log.debug(`Updating default space solution: [${solution}].`); + + await spacesService.update('default', { + ..._defaultSpace, + solution, + }); + }; + + before(async () => { + _defaultSpace = await spacesService.get('default'); + + await PageObjects.common.navigateToUrl('management', 'kibana/spaces', { + shouldUseHashForSubUrl: false, + }); + + await PageObjects.common.sleep(1000); // wait to save the setting + }); + + afterEach(async () => { + await updateSolutionDefaultSpace('classic'); // revert to not impact future tests + }); + + it('does not show the solution tour for the classic space', async () => { + await testSubjects.missingOrFail('spaceSolutionTour', { timeout: 3000 }); + }); + + it('does show the solution tour if the default space has a solution set', async () => { + await updateSolutionDefaultSpace('es'); // set a solution + await PageObjects.common.sleep(500); + await removeGlobalSettings(); // Make sure we start from a clean state + await browser.refresh(); + + await testSubjects.existOrFail('spaceSolutionTour', { timeout: 3000 }); + + await testSubjects.click('closeTourBtn'); // close the tour + await PageObjects.common.sleep(1000); // wait to save the setting + + await browser.refresh(); + await testSubjects.missingOrFail('spaceSolutionTour', { timeout: 3000 }); // The tour does not appear after refresh + }); + + it('does not show the solution tour after updating the default space from classic to solution', async () => { + await updateSolutionDefaultSpace('es'); // set a solution + await PageObjects.common.sleep(500); + await browser.refresh(); + + // The tour does not appear after refresh, even with the default space with a solution set + await testSubjects.missingOrFail('spaceSolutionTour', { timeout: 3000 }); + }); + + it('does not show the solution tour after deleting spaces and leave only the default', async () => { + await updateSolutionDefaultSpace('es'); // set a solution + + await spacesService.create({ + id: 'foo-space', + name: 'Foo Space', + disabledFeatures: [], + color: '#AABBCC', + }); + + const allSpaces = await spacesService.getAll(); + expect(allSpaces).to.have.length(2); // Make sure we have 2 spaces + + await removeGlobalSettings(); // Make sure we start from a clean state + await browser.refresh(); + + await testSubjects.missingOrFail('spaceSolutionTour', { timeout: 3000 }); + + await spacesService.delete('foo-space'); + await browser.refresh(); + + // The tour still does not appear after refresh, even with 1 space with a solution set + await testSubjects.missingOrFail('spaceSolutionTour', { timeout: 3000 }); + }); + }); + }); +} From 04efa04d6b8fc7ac6bc6996717453bd56200104a Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Oct 2024 15:30:55 +0300 Subject: [PATCH 21/84] fix: [Stateful: Home page] Wrong announcement of code editor (#195922) Closes: https://github.com/elastic/kibana/issues/195289 Closes: https://github.com/elastic/kibana/issues/195198 Closes: https://github.com/elastic/kibana/issues/195358 ## Description - The text editor must be fully accessible and functional across all devices, ensuring users can edit text using various input methods, not just a mouse. This functionality should be available in both the expanded and collapsed states. - Appropriate aria-label attribute must be assigned to elements to provide users with clear context and understanding of the type of element they are interacting with. This enhances usability and accessibility for all users. ## What was changed: - Updated the aria-label attribute for the editor button to improve accessibility. - Resolved an issue with the background color when activating full-screen mode from the dialog. - Fixed keyboard navigation for full-screen mode, enabling users to activate Edit Mode using only the keyboard. ## Screen https://github.com/user-attachments/assets/af122fab-3ce9-4a7f-b8b1-d75d39969781 --- .../impl/__snapshots__/code_editor.test.tsx.snap | 4 ++-- packages/shared-ux/code_editor/impl/code_editor.tsx | 13 ++++++++++--- .../shared-ux/code_editor/impl/editor.styles.ts | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap index e58bd37dead6c..787c5e348e51a 100644 --- a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap +++ b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap @@ -2,7 +2,7 @@ exports[` hint element should be tabable 1`] = `
is rendered 1`] = ` onMouseOver={[Function]} >
= ({ role="button" onClick={startEditing} onKeyDown={onKeyDownHint} - aria-label={ariaLabel} + aria-label={i18n.translate('sharedUXPackages.codeEditor.codeEditorEditButton', { + defaultMessage: '{codeEditorAriaLabel}, activate edit mode', + values: { + codeEditorAriaLabel: ariaLabel, + }, + })} data-test-subj={`codeEditorHint codeEditorHint--${isHintActive ? 'active' : 'inactive'}`} /> @@ -528,6 +533,7 @@ export const CodeEditor: React.FC = ({
) : null} + {accessibilityOverlayEnabled && isFullScreen && renderPrompt()} = ({ const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => { const [isFullScreen, setIsFullScreen] = useState(false); + const { euiTheme } = useEuiTheme(); const toggleFullScreen = () => { setIsFullScreen(!isFullScreen); @@ -617,12 +624,12 @@ const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => { return ( -
{children}
+
{children}
); }, - [isFullScreen] + [isFullScreen, euiTheme] ); return { diff --git a/packages/shared-ux/code_editor/impl/editor.styles.ts b/packages/shared-ux/code_editor/impl/editor.styles.ts index 62f15a4a88317..2d12cd01d031b 100644 --- a/packages/shared-ux/code_editor/impl/editor.styles.ts +++ b/packages/shared-ux/code_editor/impl/editor.styles.ts @@ -15,10 +15,11 @@ export const styles = { position: relative; height: 100%; `, - fullscreenContainer: css` + fullscreenContainer: (euiTheme: EuiThemeComputed) => css` position: absolute; left: 0; top: 0; + background: ${euiTheme.colors.body}; `, keyboardHint: (euiTheme: EuiThemeComputed) => css` position: absolute; From 23e0e1e61c6df451cc38763b53a6e2db5518b9f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:37:12 -0500 Subject: [PATCH 22/84] deps(updatecli): bump all policies (#195865) --- updatecli-compose.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/updatecli-compose.yaml b/updatecli-compose.yaml index 8ad9bd6df8afb..da43161efa6dc 100644 --- a/updatecli-compose.yaml +++ b/updatecli-compose.yaml @@ -2,13 +2,12 @@ # https://www.updatecli.io/docs/core/compose/ policies: - name: Handle ironbank bumps - policy: ghcr.io/elastic/oblt-updatecli-policies/ironbank/templates:0.3.0@sha256:b0c841d8fb294e6b58359462afbc83070dca375ac5dd0c5216c8926872a98bb1 + policy: ghcr.io/elastic/oblt-updatecli-policies/ironbank/templates:0.5.2@sha256:6a237aea2c621a675d644dd51580bd3c0cb4d48591f54f5ba1c2ba88240fa08b values: - .github/updatecli/values.d/scm.yml - .github/updatecli/values.d/ironbank.yml - - name: Update Updatecli policies - policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.4.0@sha256:254367f5b1454fd6032b88b314450cd3b6d5e8d5b6c953eb242a6464105eb869 + policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.8.0@sha256:99e9e61b501575c2c176c39f2275998d198b590a3f6b1fe829f7315f8d457e7f values: - .github/updatecli/values.d/scm.yml - - .github/updatecli/values.d/updatecli-compose.yml \ No newline at end of file + - .github/updatecli/values.d/updatecli-compose.yml From 5ed698182887e18d2aa6c4b6782cc636a45a1472 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Oct 2024 15:39:10 +0300 Subject: [PATCH 23/84] fix: [Stateful: Home page] Most Ingest your content section buttons have duplicated actions on them (#196079) Closes: #194932 ## Summary User reaches the same button two times when navigating using only keyboard and it can get confusing. Also for the user using screen reader it is also confusing if reached element is button or link. Better for element to get focus only one time when navigating in sequence from one element to another and for the user only to hear one announcement of the element, button or link (but not button link). ## What was changed?: 1. Removed extra `EuiLinkTo` wrapper 2. `EuiButton` was replaced to `EuiButtonTo` ## Screen image --- .../shared/ingestion_card/ingestion_card.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ingestion_card/ingestion_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ingestion_card/ingestion_card.tsx index 94bbc515f92bd..f935ea6803c69 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ingestion_card/ingestion_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ingestion_card/ingestion_card.tsx @@ -20,7 +20,7 @@ import { import { i18n } from '@kbn/i18n'; -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../../shared/react_router_helpers'; interface IngestionCardProps { buttonIcon: IconType; @@ -78,15 +78,25 @@ export const IngestionCard: React.FC = ({ } footer={ onClick ? ( - + {buttonLabel} ) : ( - - - {buttonLabel} - - + + {buttonLabel} + ) } /> From 8afbbc008222dee377aab568a639466d49c56306 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 15 Oct 2024 07:40:46 -0500 Subject: [PATCH 24/84] [ci] Add kibana-vm-images pipeline (#195816) --- .../kibana-vm-images.yml | 48 +++++++++++++++++++ .../locations.yml | 1 + 2 files changed, 49 insertions(+) create mode 100644 .buildkite/pipeline-resource-definitions/kibana-vm-images.yml diff --git a/.buildkite/pipeline-resource-definitions/kibana-vm-images.yml b/.buildkite/pipeline-resource-definitions/kibana-vm-images.yml new file mode 100644 index 0000000000000..dd8a6c945c455 --- /dev/null +++ b/.buildkite/pipeline-resource-definitions/kibana-vm-images.yml @@ -0,0 +1,48 @@ +# yaml-language-server: $schema=https://gist.githubusercontent.com/elasticmachine/988b80dae436cafea07d9a4a460a011d/raw/rre.schema.json +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: bk-kibana-vm-images + description: Build CI agent VM images for Kibana + links: + - url: 'https://buildkite.com/elastic/kibana-vm-images' + title: Pipeline link +spec: + type: buildkite-pipeline + owner: group:kibana-operations + system: buildkite + implementation: + apiVersion: buildkite.elastic.dev/v1 + kind: Pipeline + metadata: + name: kibana / vm images + description: Build CI agent VM images for Kibana + spec: + env: + SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' + ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' + default_branch: main + repository: elastic/ci-agent-images + pipeline_file: vm-images/.buildkite/pipeline.yml + skip_intermediate_builds: false + provider_settings: + trigger_mode: none + schedules: + daily kibana image build: + branch: main + cronline: '0 0 * * *' + env: + IMAGES_CONFIG: kibana/images.yml + message: Builds Kibana VM images daily + daily kibana fips image build: + branch: main + cronline: '0 4 * * *' # make sure this runs after the daily kibana image build + env: + BASE_IMAGES_CONFIG: 'core/images.yml,kibana/images.yml' + IMAGES_CONFIG: kibana/fips.yml + message: Builds Kibana FIPS VM image daily + teams: + kibana-operations: + access_level: MANAGE_BUILD_AND_READ + everyone: + access_level: BUILD_AND_READ diff --git a/.buildkite/pipeline-resource-definitions/locations.yml b/.buildkite/pipeline-resource-definitions/locations.yml index ce0ab7750d489..7f96bff2b51b4 100644 --- a/.buildkite/pipeline-resource-definitions/locations.yml +++ b/.buildkite/pipeline-resource-definitions/locations.yml @@ -37,6 +37,7 @@ spec: - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-serverless-quality-gates.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-serverless-release-testing.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-serverless-release.yml + - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-vm-images.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/scalability_testing-daily.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/security-solution-ess/security-solution-ess.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/security-solution-quality-gate/kibana-serverless-security-solution-quality-gate-defend-workflows.yml From 611082ab3178efcc0cd6a9e073c409e4969aa618 Mon Sep 17 00:00:00 2001 From: Julian Gernun <17549662+jcger@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:50:35 +0200 Subject: [PATCH 25/84] [Response Ops][Rules] OAS Ready Rule API (#196150) ## Summary Linked to https://github.com/elastic/kibana/issues/195182 ### muteAll - added 40x error codes to response - `public` access prop already set [here](https://github.com/elastic/kibana/blob/8545b9ccfbad97881406e56ffd96f452c94032b8/x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.ts#L28) - request schema already with description [here](https://github.com/elastic/kibana/blob/8545b9ccfbad97881406e56ffd96f452c94032b8/x-pack/plugins/alerting/common/routes/rule/apis/mute_all/schemas/v1.ts#L11) - no response schema ### unmuteAll - added 40x error codes to response - `public` access prop already set [here](https://github.com/elastic/kibana/blob/563910b672b6dbe4f9e7931e36ec41e674fe8eb3/x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.ts#L25) - params schema already with description [here](https://github.com/elastic/kibana/blob/563910b672b6dbe4f9e7931e36ec41e674fe8eb3/x-pack/plugins/alerting/common/routes/rule/apis/unmute_all/schemas/v1.ts#L11) - no response schema ### rule types - added 40x error code to response - `public` access prop already set [here](https://github.com/elastic/kibana/blob/563910b672b6dbe4f9e7931e36ec41e674fe8eb3/x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.ts#L23) - no request schema - added response schema descriptions --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- oas_docs/bundle.json | 18 ++ oas_docs/bundle.serverless.json | 18 ++ .../output/kibana.serverless.staging.yaml | 12 ++ oas_docs/output/kibana.serverless.yaml | 12 ++ oas_docs/output/kibana.staging.yaml | 12 ++ oas_docs/output/kibana.yaml | 12 ++ .../routes/rule/apis/list_types/schemas/v1.ts | 197 ++++++++++++++---- .../routes/rule/apis/list_types/rule_types.ts | 18 +- .../rule/apis/mute_all/mute_all_rule.ts | 9 + .../rule/apis/unmute_all/unmute_all_rule.ts | 9 + 10 files changed, 275 insertions(+), 42 deletions(-) diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 34a5103cba9fb..744763f3da424 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -5077,6 +5077,15 @@ "responses": { "204": { "description": "Indicates a successful call." + }, + "400": { + "description": "Indicates an invalid schema or parameters." + }, + "403": { + "description": "Indicates that this call is forbidden." + }, + "404": { + "description": "Indicates a rule with the given ID does not exist." } }, "summary": "Mute all alerts", @@ -5124,6 +5133,15 @@ "responses": { "204": { "description": "Indicates a successful call." + }, + "400": { + "description": "Indicates an invalid schema or parameters." + }, + "403": { + "description": "Indicates that this call is forbidden." + }, + "404": { + "description": "Indicates a rule with the given ID does not exist." } }, "summary": "Unmute all alerts", diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 4719fcb479bb5..b73fa1fc22841 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -5077,6 +5077,15 @@ "responses": { "204": { "description": "Indicates a successful call." + }, + "400": { + "description": "Indicates an invalid schema or parameters." + }, + "403": { + "description": "Indicates that this call is forbidden." + }, + "404": { + "description": "Indicates a rule with the given ID does not exist." } }, "summary": "Mute all alerts", @@ -5124,6 +5133,15 @@ "responses": { "204": { "description": "Indicates a successful call." + }, + "400": { + "description": "Indicates an invalid schema or parameters." + }, + "403": { + "description": "Indicates that this call is forbidden." + }, + "404": { + "description": "Indicates a rule with the given ID does not exist." } }, "summary": "Unmute all alerts", diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index 9e63182949f25..a4362db15cc7d 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -4217,6 +4217,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Mute all alerts tags: - alerting @@ -4248,6 +4254,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Unmute all alerts tags: - alerting diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 9e63182949f25..a4362db15cc7d 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -4217,6 +4217,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Mute all alerts tags: - alerting @@ -4248,6 +4254,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Unmute all alerts tags: - alerting diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index f32de75a62b26..16a6a94d34d81 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -4598,6 +4598,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Mute all alerts tags: - alerting @@ -4629,6 +4635,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Unmute all alerts tags: - alerting diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index f32de75a62b26..16a6a94d34d81 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -4598,6 +4598,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Mute all alerts tags: - alerting @@ -4629,6 +4635,12 @@ paths: responses: '204': description: Indicates a successful call. + '400': + description: Indicates an invalid schema or parameters. + '403': + description: Indicates that this call is forbidden. + '404': + description: Indicates a rule with the given ID does not exist. summary: Unmute all alerts tags: - alerting diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/list_types/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/list_types/schemas/v1.ts index bc38ef051ed90..5ea3d9219ad35 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/list_types/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/list_types/schemas/v1.ts @@ -13,58 +13,175 @@ const actionVariableSchema = schema.object({ usesPublicBaseUrl: schema.maybe(schema.boolean()), }); -const actionGroupSchema = schema.object({ - id: schema.string(), - name: schema.string(), -}); +const actionGroupSchema = schema.object( + { + id: schema.string(), + name: schema.string(), + }, + { + meta: { + description: + 'An action group to use when an alert goes from an active state to an inactive one.', + }, + } +); export const typesRulesResponseBodySchema = schema.arrayOf( schema.object({ - action_groups: schema.maybe(schema.arrayOf(actionGroupSchema)), - action_variables: schema.maybe( - schema.object({ - context: schema.maybe(schema.arrayOf(actionVariableSchema)), - state: schema.maybe(schema.arrayOf(actionVariableSchema)), - params: schema.maybe(schema.arrayOf(actionVariableSchema)), + action_groups: schema.maybe( + schema.arrayOf(actionGroupSchema, { + meta: { + description: + "An explicit list of groups for which the rule type can schedule actions, each with the action group's unique ID and human readable name. Rule actions validation uses this configuration to ensure that groups are valid.", + }, }) ), + action_variables: schema.maybe( + schema.object( + { + context: schema.maybe(schema.arrayOf(actionVariableSchema)), + state: schema.maybe(schema.arrayOf(actionVariableSchema)), + params: schema.maybe(schema.arrayOf(actionVariableSchema)), + }, + { + meta: { + description: + 'A list of action variables that the rule type makes available via context and state in action parameter templates, and a short human readable description. When you create a rule in Kibana, it uses this information to prompt you for these variables in action parameter editors.', + }, + } + ) + ), alerts: schema.maybe( - schema.object({ - context: schema.string(), - mappings: schema.maybe( - schema.object({ - dynamic: schema.maybe(schema.oneOf([schema.literal(false), schema.literal('strict')])), - fieldMap: schema.recordOf(schema.string(), schema.any()), - shouldWrite: schema.maybe(schema.boolean()), - useEcs: schema.maybe(schema.boolean()), - }) - ), - }) + schema.object( + { + context: schema.string({ + meta: { + description: 'The namespace for this rule type.', + }, + }), + mappings: schema.maybe( + schema.object({ + dynamic: schema.maybe( + schema.oneOf([schema.literal(false), schema.literal('strict')], { + meta: { + description: 'Indicates whether new fields are added dynamically.', + }, + }) + ), + fieldMap: schema.recordOf(schema.string(), schema.any(), { + meta: { + description: + 'Mapping information for each field supported in alerts as data documents for this rule type. For more information about mapping parameters, refer to the Elasticsearch documentation.', + }, + }), + shouldWrite: schema.maybe( + schema.boolean({ + meta: { + description: 'Indicates whether the rule should write out alerts as data.', + }, + }) + ), + useEcs: schema.maybe( + schema.boolean({ + meta: { + description: + 'Indicates whether to include the ECS component template for the alerts.', + }, + }) + ), + }) + ), + }, + { + meta: { + description: 'Details for writing alerts as data documents for this rule type.', + }, + } + ) ), authorized_consumers: schema.recordOf( schema.string(), - schema.object({ read: schema.boolean(), all: schema.boolean() }) + schema.object({ read: schema.boolean(), all: schema.boolean() }), + { + meta: { + description: 'The list of the plugins IDs that have access to the rule type.', + }, + } ), - category: schema.string(), - default_action_group_id: schema.string(), + category: schema.string({ + meta: { + description: + 'The rule category, which is used by features such as category-specific maintenance windows.', + }, + }), + default_action_group_id: schema.string({ + meta: { + description: 'The default identifier for the rule type group.', + }, + }), default_schedule_interval: schema.maybe(schema.string()), - does_set_recovery_context: schema.maybe(schema.boolean()), - enabled_in_license: schema.boolean(), + does_set_recovery_context: schema.maybe( + schema.boolean({ + meta: { + description: + 'Indicates whether the rule passes context variables to its recovery action.', + }, + }) + ), + enabled_in_license: schema.boolean({ + meta: { + description: + 'Indicates whether the rule type is enabled or disabled based on the subscription.', + }, + }), fieldsForAAD: schema.maybe(schema.arrayOf(schema.string())), - has_alerts_mappings: schema.boolean(), - has_fields_for_a_a_d: schema.boolean(), - id: schema.string(), - is_exportable: schema.boolean(), - minimum_license_required: schema.oneOf([ - schema.literal('basic'), - schema.literal('gold'), - schema.literal('platinum'), - schema.literal('standard'), - schema.literal('enterprise'), - schema.literal('trial'), - ]), - name: schema.string(), - producer: schema.string(), + has_alerts_mappings: schema.boolean({ + meta: { + description: 'Indicates whether the rule type has custom mappings for the alert data.', + }, + }), + has_fields_for_a_a_d: schema.boolean({ + meta: { + description: + 'Indicates whether the rule type has fields for alert as data for the alert data. ', + }, + }), + id: schema.string({ + meta: { + description: 'The unique identifier for the rule type.', + }, + }), + is_exportable: schema.boolean({ + meta: { + description: + 'Indicates whether the rule type is exportable in Stack Management > Saved Objects.', + }, + }), + minimum_license_required: schema.oneOf( + [ + schema.literal('basic'), + schema.literal('gold'), + schema.literal('platinum'), + schema.literal('standard'), + schema.literal('enterprise'), + schema.literal('trial'), + ], + { + meta: { + description: 'The subscriptions required to use the rule type.', + }, + } + ), + name: schema.string({ + meta: { + description: 'The descriptive name of the rule type.', + }, + }), + producer: schema.string({ + meta: { + description: 'An identifier for the application that produces this rule type.', + }, + }), recovery_action_group: actionGroupSchema, rule_task_timeout: schema.maybe(schema.string()), }) diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.ts b/x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.ts index d6f2ffbe9af0c..da9c62ab5f3f2 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/list_types/rule_types.ts @@ -6,7 +6,10 @@ */ import { IRouter } from '@kbn/core/server'; -import { TypesRulesResponseBodyV1 } from '../../../../../common/routes/rule/apis/list_types'; +import { + TypesRulesResponseBodyV1, + typesRulesResponseSchemaV1, +} from '../../../../../common/routes/rule/apis/list_types'; import { ILicenseState } from '../../../../lib'; import { verifyAccessAndContext } from '../../../lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../../../../types'; @@ -24,7 +27,18 @@ export const ruleTypesRoute = ( summary: `Get the rule types`, tags: ['oas-tag:alerting'], }, - validate: {}, + validate: { + request: {}, + response: { + 200: { + body: () => typesRulesResponseSchemaV1, + description: 'Indicates a successful call.', + }, + 401: { + description: 'Authorization information is missing or invalid.', + }, + }, + }, }, router.handleLegacyErrors( verifyAccessAndContext(licenseState, async function (context, req, res) { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.ts b/x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.ts index 8ac77973575bb..e9aa0e42a046f 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/mute_all/mute_all_rule.ts @@ -37,6 +37,15 @@ export const muteAllRuleRoute = ( 204: { description: 'Indicates a successful call.', }, + 400: { + description: 'Indicates an invalid schema or parameters.', + }, + 403: { + description: 'Indicates that this call is forbidden.', + }, + 404: { + description: 'Indicates a rule with the given ID does not exist.', + }, }, }, }, diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.ts index f9ab7d8d8d284..8409128da6241 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_all/unmute_all_rule.ts @@ -34,6 +34,15 @@ export const unmuteAllRuleRoute = ( 204: { description: 'Indicates a successful call.', }, + 400: { + description: 'Indicates an invalid schema or parameters.', + }, + 403: { + description: 'Indicates that this call is forbidden.', + }, + 404: { + description: 'Indicates a rule with the given ID does not exist.', + }, }, }, }, From adb558a86bafbe3567915c3fae252ff414147930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 15 Oct 2024 15:00:37 +0200 Subject: [PATCH 26/84] Change ownership `kibana-telemetry` => `kibana-core` (#196283) --- .github/CODEOWNERS | 6 +++--- packages/kbn-telemetry-tools/GUIDELINE.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7c7634aab7231..f126ad0cad658 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1396,9 +1396,9 @@ x-pack/test_serverless/api_integration/test_suites/common/security_response_head # Kibana Telemetry /.telemetryrc.json @elastic/kibana-core /x-pack/.telemetryrc.json @elastic/kibana-core -/src/plugins/telemetry/schema/ @elastic/kibana-core @elastic/kibana-telemetry -/x-pack/plugins/telemetry_collection_xpack/schema/ @elastic/kibana-core @elastic/kibana-telemetry -x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kibana-core @elastic/kibana-telemetry @shahinakmal +/src/plugins/telemetry/schema/ @elastic/kibana-core +/x-pack/plugins/telemetry_collection_xpack/schema/ @elastic/kibana-core +x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kibana-core @shahinakmal # Kibana Localization /src/dev/i18n_tools/ @elastic/kibana-localization @elastic/kibana-core diff --git a/packages/kbn-telemetry-tools/GUIDELINE.md b/packages/kbn-telemetry-tools/GUIDELINE.md index a22196bb5dc74..d5377cf47b971 100644 --- a/packages/kbn-telemetry-tools/GUIDELINE.md +++ b/packages/kbn-telemetry-tools/GUIDELINE.md @@ -103,7 +103,7 @@ The `--fix` flag will automatically update the persisted json files used by the node scripts/telemetry_check.js --fix ``` -Note that any updates to the stored json files will require a review by the kibana-telemetry team to help us update the telemetry cluster mappings and ensure your changes adhere to our best practices. +Note that any updates to the stored json files will require a review by the kibana-core team to help us update the telemetry cluster mappings and ensure your changes adhere to our best practices. ## Updating the collector schema @@ -116,7 +116,7 @@ Once youre run the changes to both the `fetch` function and the `schema` field r node scripts/telemetry_check.js --fix ``` -The `--fix` flag will automatically update the persisted json files used by the telemetry team. Note that any updates to the stored json files will require a review by the kibana-telemetry team to help us update the telemetry cluster mappings and ensure your changes adhere to our best practices. +The `--fix` flag will automatically update the persisted json files used by the telemetry team. Note that any updates to the stored json files will require a review by the kibana-core team to help us update the telemetry cluster mappings and ensure your changes adhere to our best practices. ## Writing the schema From 545f5a42f7af27bad33e272aa67eb59ac27e04ce Mon Sep 17 00:00:00 2001 From: Michael DeFazio Date: Tue, 15 Oct 2024 09:33:31 -0400 Subject: [PATCH 27/84] [Onboarding] UX Feedback - Slight Tweaks to search detail (#194873) Tweaks to search details https://github.com/user-attachments/assets/a583a9d9-b059-4ce1-beaa-f7c733feabf0 --------- Co-authored-by: Joseph McElroy Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/components/api_key_form.tsx | 2 +- .../src/providers/search_api_key_provider.tsx | 2 +- .../src/form_info_field/form_info_field.tsx | 1 + .../components/indices/details_page.tsx | 59 +++++++++---------- .../components/quick_stats/quick_stat.tsx | 9 ++- .../svl_search_index_detail_page.ts | 9 --- .../test_suites/search/search_index_detail.ts | 5 -- 7 files changed, 36 insertions(+), 51 deletions(-) diff --git a/packages/kbn-search-api-keys-components/src/components/api_key_form.tsx b/packages/kbn-search-api-keys-components/src/components/api_key_form.tsx index 02e5a46b640ac..0a94f3e336897 100644 --- a/packages/kbn-search-api-keys-components/src/components/api_key_form.tsx +++ b/packages/kbn-search-api-keys-components/src/components/api_key_form.tsx @@ -47,7 +47,7 @@ export const ApiKeyForm: React.FC = ({ hasTitle = true }) => { actions={[ = ({ childr }, [state.status, createApiKey, validateApiKey]); const value: APIKeyContext = { - displayedApiKey: state.status === Status.showHiddenKey ? API_KEY_MASK : state.apiKey, + displayedApiKey: state.status === Status.showPreviewKey ? state.apiKey : API_KEY_MASK, apiKey: state.apiKey, toggleApiKeyVisibility: handleShowKeyVisibility, updateApiKey, diff --git a/x-pack/packages/search/shared_ui/src/form_info_field/form_info_field.tsx b/x-pack/packages/search/shared_ui/src/form_info_field/form_info_field.tsx index c99daba9f4537..5a63ad81ced21 100644 --- a/x-pack/packages/search/shared_ui/src/form_info_field/form_info_field.tsx +++ b/x-pack/packages/search/shared_ui/src/form_info_field/form_info_field.tsx @@ -73,6 +73,7 @@ export const FormInfoField: React.FC = ({ { const handleDeleteIndexModal = useCallback(() => { setShowDeleteIndexModal(!isShowingDeleteModal); }, [isShowingDeleteModal]); + const { euiTheme } = useEuiTheme(); if (isInitialLoading || isMappingsInitialLoading) { return ( @@ -187,24 +187,13 @@ export const SearchIndexDetailsPage = () => { /> ) : ( <> - - navigateToIndexListPage()} - > - - - + {!isDocumentsExists ? ( { , ]} /> - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + = ({ - +

{title}

-
+
- {secondaryTitle} + + {secondaryTitle} +
diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts index ca28207f54195..ed11a09c26b66 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts @@ -23,15 +23,6 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont async expectUseInPlaygroundLinkExists() { await testSubjects.existOrFail('useInPlaygroundLink', { timeout: 5000 }); }, - async expectBackToIndicesButtonExists() { - await testSubjects.existOrFail('backToIndicesButton', { timeout: 2000 }); - }, - async clickBackToIndicesButton() { - await testSubjects.click('backToIndicesButton'); - }, - async expectBackToIndicesButtonRedirectsToListPage() { - await testSubjects.existOrFail('indicesList'); - }, async expectConnectionDetails() { await testSubjects.existOrFail('connectionDetailsEndpoint', { timeout: 2000 }); expect(await (await testSubjects.find('connectionDetailsEndpoint')).getVisibleText()).to.be( diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts index 66f15151441ae..aea757f7edea1 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts @@ -96,11 +96,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.svlSearchIndexDetailPage.expectAPIKeyToBeVisibleInCodeBlock(apiKey); }); - it('back to indices button should redirect to list page', async () => { - await pageObjects.svlSearchIndexDetailPage.expectBackToIndicesButtonExists(); - await pageObjects.svlSearchIndexDetailPage.clickBackToIndicesButton(); - await pageObjects.svlSearchIndexDetailPage.expectBackToIndicesButtonRedirectsToListPage(); - }); describe('With data', () => { before(async () => { await es.index({ From bed5c4e9fe0cf5acc2e5b3326ca306134bc18891 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 16 Oct 2024 00:47:08 +1100 Subject: [PATCH 28/84] [ES|QL] Update grammars (#196046) This PR updates the ES|QL grammars (lexer and parser) to match the latest version in Elasticsearch. --------- Co-authored-by: Stratoula Kalafateli --- packages/kbn-esql-ast/src/antlr/esql_lexer.g4 | 30 +- .../kbn-esql-ast/src/antlr/esql_lexer.interp | 27 +- .../kbn-esql-ast/src/antlr/esql_lexer.tokens | 329 ++-- packages/kbn-esql-ast/src/antlr/esql_lexer.ts | 1276 +++++++------ .../kbn-esql-ast/src/antlr/esql_parser.g4 | 19 +- .../kbn-esql-ast/src/antlr/esql_parser.interp | 16 +- .../kbn-esql-ast/src/antlr/esql_parser.tokens | 329 ++-- .../kbn-esql-ast/src/antlr/esql_parser.ts | 1643 +++++++++-------- .../src/antlr/esql_parser_listener.ts | 42 +- .../src/parser/__tests__/commands.test.ts | 18 - .../src/parser/esql_ast_builder_listener.ts | 25 - packages/kbn-esql-ast/src/parser/factories.ts | 4 +- packages/kbn-esql-ast/src/parser/parser.ts | 2 +- packages/kbn-esql-ast/src/parser/walkers.ts | 6 +- .../__tests__/basic_pretty_printer.test.ts | 9 - .../src/utils/get_esql_adhoc_dataview.ts | 2 +- .../test_suites/validation.command.from.ts | 2 +- .../test_suites/validation.command.metrics.ts | 2 +- .../esql_validation_meta_tests.json | 62 +- .../src/validation/validation.test.ts | 51 +- .../src/esql/lib/esql_theme.test.ts | 1 - .../kbn-monaco/src/esql/lib/esql_theme.ts | 3 - .../logic/esql_validator.test.ts | 2 +- 23 files changed, 1904 insertions(+), 1996 deletions(-) diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 index da58f29b5527c..80a30301d080c 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 @@ -68,7 +68,6 @@ FROM : 'from' -> pushMode(FROM_MODE); GROK : 'grok' -> pushMode(EXPRESSION_MODE); KEEP : 'keep' -> pushMode(PROJECT_MODE); LIMIT : 'limit' -> pushMode(EXPRESSION_MODE); -META : 'meta' -> pushMode(META_MODE); MV_EXPAND : 'mv_expand' -> pushMode(MVEXPAND_MODE); RENAME : 'rename' -> pushMode(RENAME_MODE); ROW : 'row' -> pushMode(EXPRESSION_MODE); @@ -309,6 +308,8 @@ mode PROJECT_MODE; PROJECT_PIPE : PIPE -> type(PIPE), popMode; PROJECT_DOT: DOT -> type(DOT); PROJECT_COMMA : COMMA -> type(COMMA); +PROJECT_PARAM : PARAM -> type(PARAM); +PROJECT_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM); fragment UNQUOTED_ID_BODY_WITH_PATTERN : (LETTER | DIGIT | UNDERSCORE | ASTERISK) @@ -342,6 +343,8 @@ RENAME_PIPE : PIPE -> type(PIPE), popMode; RENAME_ASSIGN : ASSIGN -> type(ASSIGN); RENAME_COMMA : COMMA -> type(COMMA); RENAME_DOT: DOT -> type(DOT); +RENAME_PARAM : PARAM -> type(PARAM); +RENAME_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM); AS : 'as'; @@ -413,6 +416,9 @@ ENRICH_FIELD_QUOTED_IDENTIFIER : QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER) ; +ENRICH_FIELD_PARAM : PARAM -> type(PARAM); +ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM); + ENRICH_FIELD_LINE_COMMENT : LINE_COMMENT -> channel(HIDDEN) ; @@ -428,6 +434,8 @@ ENRICH_FIELD_WS mode MVEXPAND_MODE; MVEXPAND_PIPE : PIPE -> type(PIPE), popMode; MVEXPAND_DOT: DOT -> type(DOT); +MVEXPAND_PARAM : PARAM -> type(PARAM); +MVEXPAND_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM); MVEXPAND_QUOTED_IDENTIFIER : QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER) @@ -469,26 +477,6 @@ SHOW_WS : WS -> channel(HIDDEN) ; -// -// META commands -// -mode META_MODE; -META_PIPE : PIPE -> type(PIPE), popMode; - -FUNCTIONS : 'functions'; - -META_LINE_COMMENT - : LINE_COMMENT -> channel(HIDDEN) - ; - -META_MULTILINE_COMMENT - : MULTILINE_COMMENT -> channel(HIDDEN) - ; - -META_WS - : WS -> channel(HIDDEN) - ; - mode SETTING_MODE; SETTING_CLOSING_BRACKET : CLOSING_BRACKET -> type(CLOSING_BRACKET), popMode; diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp index 8122a56884280..b5ca44826c051 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp @@ -9,7 +9,6 @@ null 'grok' 'keep' 'limit' -'meta' 'mv_expand' 'rename' 'row' @@ -104,10 +103,6 @@ null null null null -'functions' -null -null -null ':' null null @@ -137,7 +132,6 @@ FROM GROK KEEP LIMIT -META MV_EXPAND RENAME ROW @@ -232,10 +226,6 @@ INFO SHOW_LINE_COMMENT SHOW_MULTILINE_COMMENT SHOW_WS -FUNCTIONS -META_LINE_COMMENT -META_MULTILINE_COMMENT -META_WS COLON SETTING SETTING_LINE_COMMENT @@ -264,7 +254,6 @@ FROM GROK KEEP LIMIT -META MV_EXPAND RENAME ROW @@ -361,6 +350,8 @@ FROM_WS PROJECT_PIPE PROJECT_DOT PROJECT_COMMA +PROJECT_PARAM +PROJECT_NAMED_OR_POSITIONAL_PARAM UNQUOTED_ID_BODY_WITH_PATTERN UNQUOTED_ID_PATTERN ID_PATTERN @@ -371,6 +362,8 @@ RENAME_PIPE RENAME_ASSIGN RENAME_COMMA RENAME_DOT +RENAME_PARAM +RENAME_NAMED_OR_POSITIONAL_PARAM AS RENAME_ID_PATTERN RENAME_LINE_COMMENT @@ -393,11 +386,15 @@ ENRICH_FIELD_DOT ENRICH_FIELD_WITH ENRICH_FIELD_ID_PATTERN ENRICH_FIELD_QUOTED_IDENTIFIER +ENRICH_FIELD_PARAM +ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM ENRICH_FIELD_LINE_COMMENT ENRICH_FIELD_MULTILINE_COMMENT ENRICH_FIELD_WS MVEXPAND_PIPE MVEXPAND_DOT +MVEXPAND_PARAM +MVEXPAND_NAMED_OR_POSITIONAL_PARAM MVEXPAND_QUOTED_IDENTIFIER MVEXPAND_UNQUOTED_IDENTIFIER MVEXPAND_LINE_COMMENT @@ -408,11 +405,6 @@ INFO SHOW_LINE_COMMENT SHOW_MULTILINE_COMMENT SHOW_WS -META_PIPE -FUNCTIONS -META_LINE_COMMENT -META_MULTILINE_COMMENT -META_WS SETTING_CLOSING_BRACKET COLON SETTING @@ -467,7 +459,6 @@ ENRICH_MODE ENRICH_FIELD_MODE MVEXPAND_MODE SHOW_MODE -META_MODE SETTING_MODE LOOKUP_MODE LOOKUP_FIELD_MODE @@ -475,4 +466,4 @@ METRICS_MODE CLOSING_METRICS_MODE atn: -[4, 0, 125, 1474, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 4, 21, 591, 8, 21, 11, 21, 12, 21, 592, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 601, 8, 22, 10, 22, 12, 22, 604, 9, 22, 1, 22, 3, 22, 607, 8, 22, 1, 22, 3, 22, 610, 8, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 619, 8, 23, 10, 23, 12, 23, 622, 9, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 4, 24, 630, 8, 24, 11, 24, 12, 24, 631, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 3, 30, 651, 8, 30, 1, 30, 4, 30, 654, 8, 30, 11, 30, 12, 30, 655, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 665, 8, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 3, 35, 672, 8, 35, 1, 36, 1, 36, 1, 36, 5, 36, 677, 8, 36, 10, 36, 12, 36, 680, 9, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 688, 8, 36, 10, 36, 12, 36, 691, 9, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 3, 36, 698, 8, 36, 1, 36, 3, 36, 701, 8, 36, 3, 36, 703, 8, 36, 1, 37, 4, 37, 706, 8, 37, 11, 37, 12, 37, 707, 1, 38, 4, 38, 711, 8, 38, 11, 38, 12, 38, 712, 1, 38, 1, 38, 5, 38, 717, 8, 38, 10, 38, 12, 38, 720, 9, 38, 1, 38, 1, 38, 4, 38, 724, 8, 38, 11, 38, 12, 38, 725, 1, 38, 4, 38, 729, 8, 38, 11, 38, 12, 38, 730, 1, 38, 1, 38, 5, 38, 735, 8, 38, 10, 38, 12, 38, 738, 9, 38, 3, 38, 740, 8, 38, 1, 38, 1, 38, 1, 38, 1, 38, 4, 38, 746, 8, 38, 11, 38, 12, 38, 747, 1, 38, 1, 38, 3, 38, 752, 8, 38, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 3, 75, 879, 8, 75, 1, 75, 5, 75, 882, 8, 75, 10, 75, 12, 75, 885, 9, 75, 1, 75, 1, 75, 4, 75, 889, 8, 75, 11, 75, 12, 75, 890, 3, 75, 893, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 5, 78, 907, 8, 78, 10, 78, 12, 78, 910, 9, 78, 1, 78, 1, 78, 3, 78, 914, 8, 78, 1, 78, 4, 78, 917, 8, 78, 11, 78, 12, 78, 918, 3, 78, 921, 8, 78, 1, 79, 1, 79, 4, 79, 925, 8, 79, 11, 79, 12, 79, 926, 1, 79, 1, 79, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 3, 96, 1004, 8, 96, 1, 97, 4, 97, 1007, 8, 97, 11, 97, 12, 97, 1008, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 3, 106, 1048, 8, 106, 1, 107, 1, 107, 3, 107, 1052, 8, 107, 1, 107, 5, 107, 1055, 8, 107, 10, 107, 12, 107, 1058, 9, 107, 1, 107, 1, 107, 3, 107, 1062, 8, 107, 1, 107, 4, 107, 1065, 8, 107, 11, 107, 12, 107, 1066, 3, 107, 1069, 8, 107, 1, 108, 1, 108, 4, 108, 1073, 8, 108, 11, 108, 12, 108, 1074, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 126, 4, 126, 1150, 8, 126, 11, 126, 12, 126, 1151, 1, 126, 1, 126, 3, 126, 1156, 8, 126, 1, 126, 4, 126, 1159, 8, 126, 11, 126, 12, 126, 1160, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 4, 160, 1311, 8, 160, 11, 160, 12, 160, 1312, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 2, 620, 689, 0, 196, 16, 1, 18, 2, 20, 3, 22, 4, 24, 5, 26, 6, 28, 7, 30, 8, 32, 9, 34, 10, 36, 11, 38, 12, 40, 13, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 25, 66, 26, 68, 0, 70, 0, 72, 0, 74, 0, 76, 0, 78, 0, 80, 0, 82, 0, 84, 0, 86, 0, 88, 27, 90, 28, 92, 29, 94, 30, 96, 31, 98, 32, 100, 33, 102, 34, 104, 35, 106, 36, 108, 37, 110, 38, 112, 39, 114, 40, 116, 41, 118, 42, 120, 43, 122, 44, 124, 45, 126, 46, 128, 47, 130, 48, 132, 49, 134, 50, 136, 51, 138, 52, 140, 53, 142, 54, 144, 55, 146, 56, 148, 57, 150, 58, 152, 59, 154, 60, 156, 61, 158, 62, 160, 63, 162, 64, 164, 0, 166, 65, 168, 66, 170, 67, 172, 68, 174, 0, 176, 69, 178, 70, 180, 71, 182, 72, 184, 0, 186, 0, 188, 73, 190, 74, 192, 75, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 0, 206, 76, 208, 0, 210, 77, 212, 0, 214, 0, 216, 78, 218, 79, 220, 80, 222, 0, 224, 0, 226, 0, 228, 0, 230, 0, 232, 81, 234, 82, 236, 83, 238, 84, 240, 0, 242, 0, 244, 0, 246, 0, 248, 85, 250, 0, 252, 86, 254, 87, 256, 88, 258, 0, 260, 0, 262, 89, 264, 90, 266, 0, 268, 91, 270, 0, 272, 92, 274, 93, 276, 94, 278, 0, 280, 0, 282, 0, 284, 0, 286, 0, 288, 0, 290, 0, 292, 95, 294, 96, 296, 97, 298, 0, 300, 0, 302, 0, 304, 0, 306, 98, 308, 99, 310, 100, 312, 0, 314, 101, 316, 102, 318, 103, 320, 104, 322, 0, 324, 105, 326, 106, 328, 107, 330, 108, 332, 0, 334, 109, 336, 110, 338, 111, 340, 112, 342, 113, 344, 0, 346, 0, 348, 0, 350, 0, 352, 0, 354, 0, 356, 0, 358, 114, 360, 115, 362, 116, 364, 0, 366, 0, 368, 0, 370, 0, 372, 117, 374, 118, 376, 119, 378, 0, 380, 0, 382, 0, 384, 120, 386, 121, 388, 122, 390, 0, 392, 0, 394, 123, 396, 124, 398, 125, 400, 0, 402, 0, 404, 0, 406, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1501, 0, 16, 1, 0, 0, 0, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 1, 66, 1, 0, 0, 0, 1, 88, 1, 0, 0, 0, 1, 90, 1, 0, 0, 0, 1, 92, 1, 0, 0, 0, 1, 94, 1, 0, 0, 0, 1, 96, 1, 0, 0, 0, 1, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 1, 108, 1, 0, 0, 0, 1, 110, 1, 0, 0, 0, 1, 112, 1, 0, 0, 0, 1, 114, 1, 0, 0, 0, 1, 116, 1, 0, 0, 0, 1, 118, 1, 0, 0, 0, 1, 120, 1, 0, 0, 0, 1, 122, 1, 0, 0, 0, 1, 124, 1, 0, 0, 0, 1, 126, 1, 0, 0, 0, 1, 128, 1, 0, 0, 0, 1, 130, 1, 0, 0, 0, 1, 132, 1, 0, 0, 0, 1, 134, 1, 0, 0, 0, 1, 136, 1, 0, 0, 0, 1, 138, 1, 0, 0, 0, 1, 140, 1, 0, 0, 0, 1, 142, 1, 0, 0, 0, 1, 144, 1, 0, 0, 0, 1, 146, 1, 0, 0, 0, 1, 148, 1, 0, 0, 0, 1, 150, 1, 0, 0, 0, 1, 152, 1, 0, 0, 0, 1, 154, 1, 0, 0, 0, 1, 156, 1, 0, 0, 0, 1, 158, 1, 0, 0, 0, 1, 160, 1, 0, 0, 0, 1, 162, 1, 0, 0, 0, 1, 164, 1, 0, 0, 0, 1, 166, 1, 0, 0, 0, 1, 168, 1, 0, 0, 0, 1, 170, 1, 0, 0, 0, 1, 172, 1, 0, 0, 0, 1, 176, 1, 0, 0, 0, 1, 178, 1, 0, 0, 0, 1, 180, 1, 0, 0, 0, 1, 182, 1, 0, 0, 0, 2, 184, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 2, 188, 1, 0, 0, 0, 2, 190, 1, 0, 0, 0, 2, 192, 1, 0, 0, 0, 3, 194, 1, 0, 0, 0, 3, 196, 1, 0, 0, 0, 3, 198, 1, 0, 0, 0, 3, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 210, 1, 0, 0, 0, 3, 212, 1, 0, 0, 0, 3, 214, 1, 0, 0, 0, 3, 216, 1, 0, 0, 0, 3, 218, 1, 0, 0, 0, 3, 220, 1, 0, 0, 0, 4, 222, 1, 0, 0, 0, 4, 224, 1, 0, 0, 0, 4, 226, 1, 0, 0, 0, 4, 232, 1, 0, 0, 0, 4, 234, 1, 0, 0, 0, 4, 236, 1, 0, 0, 0, 4, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 5, 248, 1, 0, 0, 0, 5, 250, 1, 0, 0, 0, 5, 252, 1, 0, 0, 0, 5, 254, 1, 0, 0, 0, 5, 256, 1, 0, 0, 0, 6, 258, 1, 0, 0, 0, 6, 260, 1, 0, 0, 0, 6, 262, 1, 0, 0, 0, 6, 264, 1, 0, 0, 0, 6, 268, 1, 0, 0, 0, 6, 270, 1, 0, 0, 0, 6, 272, 1, 0, 0, 0, 6, 274, 1, 0, 0, 0, 6, 276, 1, 0, 0, 0, 7, 278, 1, 0, 0, 0, 7, 280, 1, 0, 0, 0, 7, 282, 1, 0, 0, 0, 7, 284, 1, 0, 0, 0, 7, 286, 1, 0, 0, 0, 7, 288, 1, 0, 0, 0, 7, 290, 1, 0, 0, 0, 7, 292, 1, 0, 0, 0, 7, 294, 1, 0, 0, 0, 7, 296, 1, 0, 0, 0, 8, 298, 1, 0, 0, 0, 8, 300, 1, 0, 0, 0, 8, 302, 1, 0, 0, 0, 8, 304, 1, 0, 0, 0, 8, 306, 1, 0, 0, 0, 8, 308, 1, 0, 0, 0, 8, 310, 1, 0, 0, 0, 9, 312, 1, 0, 0, 0, 9, 314, 1, 0, 0, 0, 9, 316, 1, 0, 0, 0, 9, 318, 1, 0, 0, 0, 9, 320, 1, 0, 0, 0, 10, 322, 1, 0, 0, 0, 10, 324, 1, 0, 0, 0, 10, 326, 1, 0, 0, 0, 10, 328, 1, 0, 0, 0, 10, 330, 1, 0, 0, 0, 11, 332, 1, 0, 0, 0, 11, 334, 1, 0, 0, 0, 11, 336, 1, 0, 0, 0, 11, 338, 1, 0, 0, 0, 11, 340, 1, 0, 0, 0, 11, 342, 1, 0, 0, 0, 12, 344, 1, 0, 0, 0, 12, 346, 1, 0, 0, 0, 12, 348, 1, 0, 0, 0, 12, 350, 1, 0, 0, 0, 12, 352, 1, 0, 0, 0, 12, 354, 1, 0, 0, 0, 12, 356, 1, 0, 0, 0, 12, 358, 1, 0, 0, 0, 12, 360, 1, 0, 0, 0, 12, 362, 1, 0, 0, 0, 13, 364, 1, 0, 0, 0, 13, 366, 1, 0, 0, 0, 13, 368, 1, 0, 0, 0, 13, 370, 1, 0, 0, 0, 13, 372, 1, 0, 0, 0, 13, 374, 1, 0, 0, 0, 13, 376, 1, 0, 0, 0, 14, 378, 1, 0, 0, 0, 14, 380, 1, 0, 0, 0, 14, 382, 1, 0, 0, 0, 14, 384, 1, 0, 0, 0, 14, 386, 1, 0, 0, 0, 14, 388, 1, 0, 0, 0, 15, 390, 1, 0, 0, 0, 15, 392, 1, 0, 0, 0, 15, 394, 1, 0, 0, 0, 15, 396, 1, 0, 0, 0, 15, 398, 1, 0, 0, 0, 15, 400, 1, 0, 0, 0, 15, 402, 1, 0, 0, 0, 15, 404, 1, 0, 0, 0, 15, 406, 1, 0, 0, 0, 16, 408, 1, 0, 0, 0, 18, 418, 1, 0, 0, 0, 20, 425, 1, 0, 0, 0, 22, 434, 1, 0, 0, 0, 24, 441, 1, 0, 0, 0, 26, 451, 1, 0, 0, 0, 28, 458, 1, 0, 0, 0, 30, 465, 1, 0, 0, 0, 32, 472, 1, 0, 0, 0, 34, 480, 1, 0, 0, 0, 36, 487, 1, 0, 0, 0, 38, 499, 1, 0, 0, 0, 40, 508, 1, 0, 0, 0, 42, 514, 1, 0, 0, 0, 44, 521, 1, 0, 0, 0, 46, 528, 1, 0, 0, 0, 48, 536, 1, 0, 0, 0, 50, 544, 1, 0, 0, 0, 52, 559, 1, 0, 0, 0, 54, 569, 1, 0, 0, 0, 56, 578, 1, 0, 0, 0, 58, 590, 1, 0, 0, 0, 60, 596, 1, 0, 0, 0, 62, 613, 1, 0, 0, 0, 64, 629, 1, 0, 0, 0, 66, 635, 1, 0, 0, 0, 68, 639, 1, 0, 0, 0, 70, 641, 1, 0, 0, 0, 72, 643, 1, 0, 0, 0, 74, 646, 1, 0, 0, 0, 76, 648, 1, 0, 0, 0, 78, 657, 1, 0, 0, 0, 80, 659, 1, 0, 0, 0, 82, 664, 1, 0, 0, 0, 84, 666, 1, 0, 0, 0, 86, 671, 1, 0, 0, 0, 88, 702, 1, 0, 0, 0, 90, 705, 1, 0, 0, 0, 92, 751, 1, 0, 0, 0, 94, 753, 1, 0, 0, 0, 96, 756, 1, 0, 0, 0, 98, 760, 1, 0, 0, 0, 100, 764, 1, 0, 0, 0, 102, 766, 1, 0, 0, 0, 104, 769, 1, 0, 0, 0, 106, 771, 1, 0, 0, 0, 108, 776, 1, 0, 0, 0, 110, 778, 1, 0, 0, 0, 112, 784, 1, 0, 0, 0, 114, 790, 1, 0, 0, 0, 116, 793, 1, 0, 0, 0, 118, 796, 1, 0, 0, 0, 120, 801, 1, 0, 0, 0, 122, 806, 1, 0, 0, 0, 124, 808, 1, 0, 0, 0, 126, 812, 1, 0, 0, 0, 128, 817, 1, 0, 0, 0, 130, 823, 1, 0, 0, 0, 132, 826, 1, 0, 0, 0, 134, 828, 1, 0, 0, 0, 136, 834, 1, 0, 0, 0, 138, 836, 1, 0, 0, 0, 140, 841, 1, 0, 0, 0, 142, 844, 1, 0, 0, 0, 144, 847, 1, 0, 0, 0, 146, 850, 1, 0, 0, 0, 148, 852, 1, 0, 0, 0, 150, 855, 1, 0, 0, 0, 152, 857, 1, 0, 0, 0, 154, 860, 1, 0, 0, 0, 156, 862, 1, 0, 0, 0, 158, 864, 1, 0, 0, 0, 160, 866, 1, 0, 0, 0, 162, 868, 1, 0, 0, 0, 164, 870, 1, 0, 0, 0, 166, 892, 1, 0, 0, 0, 168, 894, 1, 0, 0, 0, 170, 899, 1, 0, 0, 0, 172, 920, 1, 0, 0, 0, 174, 922, 1, 0, 0, 0, 176, 930, 1, 0, 0, 0, 178, 932, 1, 0, 0, 0, 180, 936, 1, 0, 0, 0, 182, 940, 1, 0, 0, 0, 184, 944, 1, 0, 0, 0, 186, 949, 1, 0, 0, 0, 188, 954, 1, 0, 0, 0, 190, 958, 1, 0, 0, 0, 192, 962, 1, 0, 0, 0, 194, 966, 1, 0, 0, 0, 196, 971, 1, 0, 0, 0, 198, 975, 1, 0, 0, 0, 200, 979, 1, 0, 0, 0, 202, 983, 1, 0, 0, 0, 204, 987, 1, 0, 0, 0, 206, 991, 1, 0, 0, 0, 208, 1003, 1, 0, 0, 0, 210, 1006, 1, 0, 0, 0, 212, 1010, 1, 0, 0, 0, 214, 1014, 1, 0, 0, 0, 216, 1018, 1, 0, 0, 0, 218, 1022, 1, 0, 0, 0, 220, 1026, 1, 0, 0, 0, 222, 1030, 1, 0, 0, 0, 224, 1035, 1, 0, 0, 0, 226, 1039, 1, 0, 0, 0, 228, 1047, 1, 0, 0, 0, 230, 1068, 1, 0, 0, 0, 232, 1072, 1, 0, 0, 0, 234, 1076, 1, 0, 0, 0, 236, 1080, 1, 0, 0, 0, 238, 1084, 1, 0, 0, 0, 240, 1088, 1, 0, 0, 0, 242, 1093, 1, 0, 0, 0, 244, 1097, 1, 0, 0, 0, 246, 1101, 1, 0, 0, 0, 248, 1105, 1, 0, 0, 0, 250, 1108, 1, 0, 0, 0, 252, 1112, 1, 0, 0, 0, 254, 1116, 1, 0, 0, 0, 256, 1120, 1, 0, 0, 0, 258, 1124, 1, 0, 0, 0, 260, 1129, 1, 0, 0, 0, 262, 1134, 1, 0, 0, 0, 264, 1139, 1, 0, 0, 0, 266, 1146, 1, 0, 0, 0, 268, 1155, 1, 0, 0, 0, 270, 1162, 1, 0, 0, 0, 272, 1166, 1, 0, 0, 0, 274, 1170, 1, 0, 0, 0, 276, 1174, 1, 0, 0, 0, 278, 1178, 1, 0, 0, 0, 280, 1184, 1, 0, 0, 0, 282, 1188, 1, 0, 0, 0, 284, 1192, 1, 0, 0, 0, 286, 1196, 1, 0, 0, 0, 288, 1200, 1, 0, 0, 0, 290, 1204, 1, 0, 0, 0, 292, 1208, 1, 0, 0, 0, 294, 1212, 1, 0, 0, 0, 296, 1216, 1, 0, 0, 0, 298, 1220, 1, 0, 0, 0, 300, 1225, 1, 0, 0, 0, 302, 1229, 1, 0, 0, 0, 304, 1233, 1, 0, 0, 0, 306, 1237, 1, 0, 0, 0, 308, 1241, 1, 0, 0, 0, 310, 1245, 1, 0, 0, 0, 312, 1249, 1, 0, 0, 0, 314, 1254, 1, 0, 0, 0, 316, 1259, 1, 0, 0, 0, 318, 1263, 1, 0, 0, 0, 320, 1267, 1, 0, 0, 0, 322, 1271, 1, 0, 0, 0, 324, 1276, 1, 0, 0, 0, 326, 1286, 1, 0, 0, 0, 328, 1290, 1, 0, 0, 0, 330, 1294, 1, 0, 0, 0, 332, 1298, 1, 0, 0, 0, 334, 1303, 1, 0, 0, 0, 336, 1310, 1, 0, 0, 0, 338, 1314, 1, 0, 0, 0, 340, 1318, 1, 0, 0, 0, 342, 1322, 1, 0, 0, 0, 344, 1326, 1, 0, 0, 0, 346, 1331, 1, 0, 0, 0, 348, 1335, 1, 0, 0, 0, 350, 1339, 1, 0, 0, 0, 352, 1343, 1, 0, 0, 0, 354, 1348, 1, 0, 0, 0, 356, 1352, 1, 0, 0, 0, 358, 1356, 1, 0, 0, 0, 360, 1360, 1, 0, 0, 0, 362, 1364, 1, 0, 0, 0, 364, 1368, 1, 0, 0, 0, 366, 1374, 1, 0, 0, 0, 368, 1378, 1, 0, 0, 0, 370, 1382, 1, 0, 0, 0, 372, 1386, 1, 0, 0, 0, 374, 1390, 1, 0, 0, 0, 376, 1394, 1, 0, 0, 0, 378, 1398, 1, 0, 0, 0, 380, 1403, 1, 0, 0, 0, 382, 1409, 1, 0, 0, 0, 384, 1415, 1, 0, 0, 0, 386, 1419, 1, 0, 0, 0, 388, 1423, 1, 0, 0, 0, 390, 1427, 1, 0, 0, 0, 392, 1433, 1, 0, 0, 0, 394, 1439, 1, 0, 0, 0, 396, 1443, 1, 0, 0, 0, 398, 1447, 1, 0, 0, 0, 400, 1451, 1, 0, 0, 0, 402, 1457, 1, 0, 0, 0, 404, 1463, 1, 0, 0, 0, 406, 1469, 1, 0, 0, 0, 408, 409, 7, 0, 0, 0, 409, 410, 7, 1, 0, 0, 410, 411, 7, 2, 0, 0, 411, 412, 7, 2, 0, 0, 412, 413, 7, 3, 0, 0, 413, 414, 7, 4, 0, 0, 414, 415, 7, 5, 0, 0, 415, 416, 1, 0, 0, 0, 416, 417, 6, 0, 0, 0, 417, 17, 1, 0, 0, 0, 418, 419, 7, 0, 0, 0, 419, 420, 7, 6, 0, 0, 420, 421, 7, 7, 0, 0, 421, 422, 7, 8, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 6, 1, 1, 0, 424, 19, 1, 0, 0, 0, 425, 426, 7, 3, 0, 0, 426, 427, 7, 9, 0, 0, 427, 428, 7, 6, 0, 0, 428, 429, 7, 1, 0, 0, 429, 430, 7, 4, 0, 0, 430, 431, 7, 10, 0, 0, 431, 432, 1, 0, 0, 0, 432, 433, 6, 2, 2, 0, 433, 21, 1, 0, 0, 0, 434, 435, 7, 3, 0, 0, 435, 436, 7, 11, 0, 0, 436, 437, 7, 12, 0, 0, 437, 438, 7, 13, 0, 0, 438, 439, 1, 0, 0, 0, 439, 440, 6, 3, 0, 0, 440, 23, 1, 0, 0, 0, 441, 442, 7, 3, 0, 0, 442, 443, 7, 14, 0, 0, 443, 444, 7, 8, 0, 0, 444, 445, 7, 13, 0, 0, 445, 446, 7, 12, 0, 0, 446, 447, 7, 1, 0, 0, 447, 448, 7, 9, 0, 0, 448, 449, 1, 0, 0, 0, 449, 450, 6, 4, 3, 0, 450, 25, 1, 0, 0, 0, 451, 452, 7, 15, 0, 0, 452, 453, 7, 6, 0, 0, 453, 454, 7, 7, 0, 0, 454, 455, 7, 16, 0, 0, 455, 456, 1, 0, 0, 0, 456, 457, 6, 5, 4, 0, 457, 27, 1, 0, 0, 0, 458, 459, 7, 17, 0, 0, 459, 460, 7, 6, 0, 0, 460, 461, 7, 7, 0, 0, 461, 462, 7, 18, 0, 0, 462, 463, 1, 0, 0, 0, 463, 464, 6, 6, 0, 0, 464, 29, 1, 0, 0, 0, 465, 466, 7, 18, 0, 0, 466, 467, 7, 3, 0, 0, 467, 468, 7, 3, 0, 0, 468, 469, 7, 8, 0, 0, 469, 470, 1, 0, 0, 0, 470, 471, 6, 7, 1, 0, 471, 31, 1, 0, 0, 0, 472, 473, 7, 13, 0, 0, 473, 474, 7, 1, 0, 0, 474, 475, 7, 16, 0, 0, 475, 476, 7, 1, 0, 0, 476, 477, 7, 5, 0, 0, 477, 478, 1, 0, 0, 0, 478, 479, 6, 8, 0, 0, 479, 33, 1, 0, 0, 0, 480, 481, 7, 16, 0, 0, 481, 482, 7, 3, 0, 0, 482, 483, 7, 5, 0, 0, 483, 484, 7, 12, 0, 0, 484, 485, 1, 0, 0, 0, 485, 486, 6, 9, 5, 0, 486, 35, 1, 0, 0, 0, 487, 488, 7, 16, 0, 0, 488, 489, 7, 11, 0, 0, 489, 490, 5, 95, 0, 0, 490, 491, 7, 3, 0, 0, 491, 492, 7, 14, 0, 0, 492, 493, 7, 8, 0, 0, 493, 494, 7, 12, 0, 0, 494, 495, 7, 9, 0, 0, 495, 496, 7, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 498, 6, 10, 6, 0, 498, 37, 1, 0, 0, 0, 499, 500, 7, 6, 0, 0, 500, 501, 7, 3, 0, 0, 501, 502, 7, 9, 0, 0, 502, 503, 7, 12, 0, 0, 503, 504, 7, 16, 0, 0, 504, 505, 7, 3, 0, 0, 505, 506, 1, 0, 0, 0, 506, 507, 6, 11, 7, 0, 507, 39, 1, 0, 0, 0, 508, 509, 7, 6, 0, 0, 509, 510, 7, 7, 0, 0, 510, 511, 7, 19, 0, 0, 511, 512, 1, 0, 0, 0, 512, 513, 6, 12, 0, 0, 513, 41, 1, 0, 0, 0, 514, 515, 7, 2, 0, 0, 515, 516, 7, 10, 0, 0, 516, 517, 7, 7, 0, 0, 517, 518, 7, 19, 0, 0, 518, 519, 1, 0, 0, 0, 519, 520, 6, 13, 8, 0, 520, 43, 1, 0, 0, 0, 521, 522, 7, 2, 0, 0, 522, 523, 7, 7, 0, 0, 523, 524, 7, 6, 0, 0, 524, 525, 7, 5, 0, 0, 525, 526, 1, 0, 0, 0, 526, 527, 6, 14, 0, 0, 527, 45, 1, 0, 0, 0, 528, 529, 7, 2, 0, 0, 529, 530, 7, 5, 0, 0, 530, 531, 7, 12, 0, 0, 531, 532, 7, 5, 0, 0, 532, 533, 7, 2, 0, 0, 533, 534, 1, 0, 0, 0, 534, 535, 6, 15, 0, 0, 535, 47, 1, 0, 0, 0, 536, 537, 7, 19, 0, 0, 537, 538, 7, 10, 0, 0, 538, 539, 7, 3, 0, 0, 539, 540, 7, 6, 0, 0, 540, 541, 7, 3, 0, 0, 541, 542, 1, 0, 0, 0, 542, 543, 6, 16, 0, 0, 543, 49, 1, 0, 0, 0, 544, 545, 4, 17, 0, 0, 545, 546, 7, 1, 0, 0, 546, 547, 7, 9, 0, 0, 547, 548, 7, 13, 0, 0, 548, 549, 7, 1, 0, 0, 549, 550, 7, 9, 0, 0, 550, 551, 7, 3, 0, 0, 551, 552, 7, 2, 0, 0, 552, 553, 7, 5, 0, 0, 553, 554, 7, 12, 0, 0, 554, 555, 7, 5, 0, 0, 555, 556, 7, 2, 0, 0, 556, 557, 1, 0, 0, 0, 557, 558, 6, 17, 0, 0, 558, 51, 1, 0, 0, 0, 559, 560, 4, 18, 1, 0, 560, 561, 7, 13, 0, 0, 561, 562, 7, 7, 0, 0, 562, 563, 7, 7, 0, 0, 563, 564, 7, 18, 0, 0, 564, 565, 7, 20, 0, 0, 565, 566, 7, 8, 0, 0, 566, 567, 1, 0, 0, 0, 567, 568, 6, 18, 9, 0, 568, 53, 1, 0, 0, 0, 569, 570, 4, 19, 2, 0, 570, 571, 7, 16, 0, 0, 571, 572, 7, 12, 0, 0, 572, 573, 7, 5, 0, 0, 573, 574, 7, 4, 0, 0, 574, 575, 7, 10, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 6, 19, 0, 0, 577, 55, 1, 0, 0, 0, 578, 579, 4, 20, 3, 0, 579, 580, 7, 16, 0, 0, 580, 581, 7, 3, 0, 0, 581, 582, 7, 5, 0, 0, 582, 583, 7, 6, 0, 0, 583, 584, 7, 1, 0, 0, 584, 585, 7, 4, 0, 0, 585, 586, 7, 2, 0, 0, 586, 587, 1, 0, 0, 0, 587, 588, 6, 20, 10, 0, 588, 57, 1, 0, 0, 0, 589, 591, 8, 21, 0, 0, 590, 589, 1, 0, 0, 0, 591, 592, 1, 0, 0, 0, 592, 590, 1, 0, 0, 0, 592, 593, 1, 0, 0, 0, 593, 594, 1, 0, 0, 0, 594, 595, 6, 21, 0, 0, 595, 59, 1, 0, 0, 0, 596, 597, 5, 47, 0, 0, 597, 598, 5, 47, 0, 0, 598, 602, 1, 0, 0, 0, 599, 601, 8, 22, 0, 0, 600, 599, 1, 0, 0, 0, 601, 604, 1, 0, 0, 0, 602, 600, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, 606, 1, 0, 0, 0, 604, 602, 1, 0, 0, 0, 605, 607, 5, 13, 0, 0, 606, 605, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 609, 1, 0, 0, 0, 608, 610, 5, 10, 0, 0, 609, 608, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 612, 6, 22, 11, 0, 612, 61, 1, 0, 0, 0, 613, 614, 5, 47, 0, 0, 614, 615, 5, 42, 0, 0, 615, 620, 1, 0, 0, 0, 616, 619, 3, 62, 23, 0, 617, 619, 9, 0, 0, 0, 618, 616, 1, 0, 0, 0, 618, 617, 1, 0, 0, 0, 619, 622, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 623, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 624, 5, 42, 0, 0, 624, 625, 5, 47, 0, 0, 625, 626, 1, 0, 0, 0, 626, 627, 6, 23, 11, 0, 627, 63, 1, 0, 0, 0, 628, 630, 7, 23, 0, 0, 629, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 629, 1, 0, 0, 0, 631, 632, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 634, 6, 24, 11, 0, 634, 65, 1, 0, 0, 0, 635, 636, 5, 124, 0, 0, 636, 637, 1, 0, 0, 0, 637, 638, 6, 25, 12, 0, 638, 67, 1, 0, 0, 0, 639, 640, 7, 24, 0, 0, 640, 69, 1, 0, 0, 0, 641, 642, 7, 25, 0, 0, 642, 71, 1, 0, 0, 0, 643, 644, 5, 92, 0, 0, 644, 645, 7, 26, 0, 0, 645, 73, 1, 0, 0, 0, 646, 647, 8, 27, 0, 0, 647, 75, 1, 0, 0, 0, 648, 650, 7, 3, 0, 0, 649, 651, 7, 28, 0, 0, 650, 649, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 653, 1, 0, 0, 0, 652, 654, 3, 68, 26, 0, 653, 652, 1, 0, 0, 0, 654, 655, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 655, 656, 1, 0, 0, 0, 656, 77, 1, 0, 0, 0, 657, 658, 5, 64, 0, 0, 658, 79, 1, 0, 0, 0, 659, 660, 5, 96, 0, 0, 660, 81, 1, 0, 0, 0, 661, 665, 8, 29, 0, 0, 662, 663, 5, 96, 0, 0, 663, 665, 5, 96, 0, 0, 664, 661, 1, 0, 0, 0, 664, 662, 1, 0, 0, 0, 665, 83, 1, 0, 0, 0, 666, 667, 5, 95, 0, 0, 667, 85, 1, 0, 0, 0, 668, 672, 3, 70, 27, 0, 669, 672, 3, 68, 26, 0, 670, 672, 3, 84, 34, 0, 671, 668, 1, 0, 0, 0, 671, 669, 1, 0, 0, 0, 671, 670, 1, 0, 0, 0, 672, 87, 1, 0, 0, 0, 673, 678, 5, 34, 0, 0, 674, 677, 3, 72, 28, 0, 675, 677, 3, 74, 29, 0, 676, 674, 1, 0, 0, 0, 676, 675, 1, 0, 0, 0, 677, 680, 1, 0, 0, 0, 678, 676, 1, 0, 0, 0, 678, 679, 1, 0, 0, 0, 679, 681, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 681, 703, 5, 34, 0, 0, 682, 683, 5, 34, 0, 0, 683, 684, 5, 34, 0, 0, 684, 685, 5, 34, 0, 0, 685, 689, 1, 0, 0, 0, 686, 688, 8, 22, 0, 0, 687, 686, 1, 0, 0, 0, 688, 691, 1, 0, 0, 0, 689, 690, 1, 0, 0, 0, 689, 687, 1, 0, 0, 0, 690, 692, 1, 0, 0, 0, 691, 689, 1, 0, 0, 0, 692, 693, 5, 34, 0, 0, 693, 694, 5, 34, 0, 0, 694, 695, 5, 34, 0, 0, 695, 697, 1, 0, 0, 0, 696, 698, 5, 34, 0, 0, 697, 696, 1, 0, 0, 0, 697, 698, 1, 0, 0, 0, 698, 700, 1, 0, 0, 0, 699, 701, 5, 34, 0, 0, 700, 699, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 703, 1, 0, 0, 0, 702, 673, 1, 0, 0, 0, 702, 682, 1, 0, 0, 0, 703, 89, 1, 0, 0, 0, 704, 706, 3, 68, 26, 0, 705, 704, 1, 0, 0, 0, 706, 707, 1, 0, 0, 0, 707, 705, 1, 0, 0, 0, 707, 708, 1, 0, 0, 0, 708, 91, 1, 0, 0, 0, 709, 711, 3, 68, 26, 0, 710, 709, 1, 0, 0, 0, 711, 712, 1, 0, 0, 0, 712, 710, 1, 0, 0, 0, 712, 713, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 718, 3, 108, 46, 0, 715, 717, 3, 68, 26, 0, 716, 715, 1, 0, 0, 0, 717, 720, 1, 0, 0, 0, 718, 716, 1, 0, 0, 0, 718, 719, 1, 0, 0, 0, 719, 752, 1, 0, 0, 0, 720, 718, 1, 0, 0, 0, 721, 723, 3, 108, 46, 0, 722, 724, 3, 68, 26, 0, 723, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 723, 1, 0, 0, 0, 725, 726, 1, 0, 0, 0, 726, 752, 1, 0, 0, 0, 727, 729, 3, 68, 26, 0, 728, 727, 1, 0, 0, 0, 729, 730, 1, 0, 0, 0, 730, 728, 1, 0, 0, 0, 730, 731, 1, 0, 0, 0, 731, 739, 1, 0, 0, 0, 732, 736, 3, 108, 46, 0, 733, 735, 3, 68, 26, 0, 734, 733, 1, 0, 0, 0, 735, 738, 1, 0, 0, 0, 736, 734, 1, 0, 0, 0, 736, 737, 1, 0, 0, 0, 737, 740, 1, 0, 0, 0, 738, 736, 1, 0, 0, 0, 739, 732, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 3, 76, 30, 0, 742, 752, 1, 0, 0, 0, 743, 745, 3, 108, 46, 0, 744, 746, 3, 68, 26, 0, 745, 744, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 745, 1, 0, 0, 0, 747, 748, 1, 0, 0, 0, 748, 749, 1, 0, 0, 0, 749, 750, 3, 76, 30, 0, 750, 752, 1, 0, 0, 0, 751, 710, 1, 0, 0, 0, 751, 721, 1, 0, 0, 0, 751, 728, 1, 0, 0, 0, 751, 743, 1, 0, 0, 0, 752, 93, 1, 0, 0, 0, 753, 754, 7, 30, 0, 0, 754, 755, 7, 31, 0, 0, 755, 95, 1, 0, 0, 0, 756, 757, 7, 12, 0, 0, 757, 758, 7, 9, 0, 0, 758, 759, 7, 0, 0, 0, 759, 97, 1, 0, 0, 0, 760, 761, 7, 12, 0, 0, 761, 762, 7, 2, 0, 0, 762, 763, 7, 4, 0, 0, 763, 99, 1, 0, 0, 0, 764, 765, 5, 61, 0, 0, 765, 101, 1, 0, 0, 0, 766, 767, 5, 58, 0, 0, 767, 768, 5, 58, 0, 0, 768, 103, 1, 0, 0, 0, 769, 770, 5, 44, 0, 0, 770, 105, 1, 0, 0, 0, 771, 772, 7, 0, 0, 0, 772, 773, 7, 3, 0, 0, 773, 774, 7, 2, 0, 0, 774, 775, 7, 4, 0, 0, 775, 107, 1, 0, 0, 0, 776, 777, 5, 46, 0, 0, 777, 109, 1, 0, 0, 0, 778, 779, 7, 15, 0, 0, 779, 780, 7, 12, 0, 0, 780, 781, 7, 13, 0, 0, 781, 782, 7, 2, 0, 0, 782, 783, 7, 3, 0, 0, 783, 111, 1, 0, 0, 0, 784, 785, 7, 15, 0, 0, 785, 786, 7, 1, 0, 0, 786, 787, 7, 6, 0, 0, 787, 788, 7, 2, 0, 0, 788, 789, 7, 5, 0, 0, 789, 113, 1, 0, 0, 0, 790, 791, 7, 1, 0, 0, 791, 792, 7, 9, 0, 0, 792, 115, 1, 0, 0, 0, 793, 794, 7, 1, 0, 0, 794, 795, 7, 2, 0, 0, 795, 117, 1, 0, 0, 0, 796, 797, 7, 13, 0, 0, 797, 798, 7, 12, 0, 0, 798, 799, 7, 2, 0, 0, 799, 800, 7, 5, 0, 0, 800, 119, 1, 0, 0, 0, 801, 802, 7, 13, 0, 0, 802, 803, 7, 1, 0, 0, 803, 804, 7, 18, 0, 0, 804, 805, 7, 3, 0, 0, 805, 121, 1, 0, 0, 0, 806, 807, 5, 40, 0, 0, 807, 123, 1, 0, 0, 0, 808, 809, 7, 9, 0, 0, 809, 810, 7, 7, 0, 0, 810, 811, 7, 5, 0, 0, 811, 125, 1, 0, 0, 0, 812, 813, 7, 9, 0, 0, 813, 814, 7, 20, 0, 0, 814, 815, 7, 13, 0, 0, 815, 816, 7, 13, 0, 0, 816, 127, 1, 0, 0, 0, 817, 818, 7, 9, 0, 0, 818, 819, 7, 20, 0, 0, 819, 820, 7, 13, 0, 0, 820, 821, 7, 13, 0, 0, 821, 822, 7, 2, 0, 0, 822, 129, 1, 0, 0, 0, 823, 824, 7, 7, 0, 0, 824, 825, 7, 6, 0, 0, 825, 131, 1, 0, 0, 0, 826, 827, 5, 63, 0, 0, 827, 133, 1, 0, 0, 0, 828, 829, 7, 6, 0, 0, 829, 830, 7, 13, 0, 0, 830, 831, 7, 1, 0, 0, 831, 832, 7, 18, 0, 0, 832, 833, 7, 3, 0, 0, 833, 135, 1, 0, 0, 0, 834, 835, 5, 41, 0, 0, 835, 137, 1, 0, 0, 0, 836, 837, 7, 5, 0, 0, 837, 838, 7, 6, 0, 0, 838, 839, 7, 20, 0, 0, 839, 840, 7, 3, 0, 0, 840, 139, 1, 0, 0, 0, 841, 842, 5, 61, 0, 0, 842, 843, 5, 61, 0, 0, 843, 141, 1, 0, 0, 0, 844, 845, 5, 61, 0, 0, 845, 846, 5, 126, 0, 0, 846, 143, 1, 0, 0, 0, 847, 848, 5, 33, 0, 0, 848, 849, 5, 61, 0, 0, 849, 145, 1, 0, 0, 0, 850, 851, 5, 60, 0, 0, 851, 147, 1, 0, 0, 0, 852, 853, 5, 60, 0, 0, 853, 854, 5, 61, 0, 0, 854, 149, 1, 0, 0, 0, 855, 856, 5, 62, 0, 0, 856, 151, 1, 0, 0, 0, 857, 858, 5, 62, 0, 0, 858, 859, 5, 61, 0, 0, 859, 153, 1, 0, 0, 0, 860, 861, 5, 43, 0, 0, 861, 155, 1, 0, 0, 0, 862, 863, 5, 45, 0, 0, 863, 157, 1, 0, 0, 0, 864, 865, 5, 42, 0, 0, 865, 159, 1, 0, 0, 0, 866, 867, 5, 47, 0, 0, 867, 161, 1, 0, 0, 0, 868, 869, 5, 37, 0, 0, 869, 163, 1, 0, 0, 0, 870, 871, 4, 74, 4, 0, 871, 872, 3, 54, 19, 0, 872, 873, 1, 0, 0, 0, 873, 874, 6, 74, 13, 0, 874, 165, 1, 0, 0, 0, 875, 878, 3, 132, 58, 0, 876, 879, 3, 70, 27, 0, 877, 879, 3, 84, 34, 0, 878, 876, 1, 0, 0, 0, 878, 877, 1, 0, 0, 0, 879, 883, 1, 0, 0, 0, 880, 882, 3, 86, 35, 0, 881, 880, 1, 0, 0, 0, 882, 885, 1, 0, 0, 0, 883, 881, 1, 0, 0, 0, 883, 884, 1, 0, 0, 0, 884, 893, 1, 0, 0, 0, 885, 883, 1, 0, 0, 0, 886, 888, 3, 132, 58, 0, 887, 889, 3, 68, 26, 0, 888, 887, 1, 0, 0, 0, 889, 890, 1, 0, 0, 0, 890, 888, 1, 0, 0, 0, 890, 891, 1, 0, 0, 0, 891, 893, 1, 0, 0, 0, 892, 875, 1, 0, 0, 0, 892, 886, 1, 0, 0, 0, 893, 167, 1, 0, 0, 0, 894, 895, 5, 91, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 76, 0, 0, 897, 898, 6, 76, 0, 0, 898, 169, 1, 0, 0, 0, 899, 900, 5, 93, 0, 0, 900, 901, 1, 0, 0, 0, 901, 902, 6, 77, 12, 0, 902, 903, 6, 77, 12, 0, 903, 171, 1, 0, 0, 0, 904, 908, 3, 70, 27, 0, 905, 907, 3, 86, 35, 0, 906, 905, 1, 0, 0, 0, 907, 910, 1, 0, 0, 0, 908, 906, 1, 0, 0, 0, 908, 909, 1, 0, 0, 0, 909, 921, 1, 0, 0, 0, 910, 908, 1, 0, 0, 0, 911, 914, 3, 84, 34, 0, 912, 914, 3, 78, 31, 0, 913, 911, 1, 0, 0, 0, 913, 912, 1, 0, 0, 0, 914, 916, 1, 0, 0, 0, 915, 917, 3, 86, 35, 0, 916, 915, 1, 0, 0, 0, 917, 918, 1, 0, 0, 0, 918, 916, 1, 0, 0, 0, 918, 919, 1, 0, 0, 0, 919, 921, 1, 0, 0, 0, 920, 904, 1, 0, 0, 0, 920, 913, 1, 0, 0, 0, 921, 173, 1, 0, 0, 0, 922, 924, 3, 80, 32, 0, 923, 925, 3, 82, 33, 0, 924, 923, 1, 0, 0, 0, 925, 926, 1, 0, 0, 0, 926, 924, 1, 0, 0, 0, 926, 927, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 929, 3, 80, 32, 0, 929, 175, 1, 0, 0, 0, 930, 931, 3, 174, 79, 0, 931, 177, 1, 0, 0, 0, 932, 933, 3, 60, 22, 0, 933, 934, 1, 0, 0, 0, 934, 935, 6, 81, 11, 0, 935, 179, 1, 0, 0, 0, 936, 937, 3, 62, 23, 0, 937, 938, 1, 0, 0, 0, 938, 939, 6, 82, 11, 0, 939, 181, 1, 0, 0, 0, 940, 941, 3, 64, 24, 0, 941, 942, 1, 0, 0, 0, 942, 943, 6, 83, 11, 0, 943, 183, 1, 0, 0, 0, 944, 945, 3, 168, 76, 0, 945, 946, 1, 0, 0, 0, 946, 947, 6, 84, 14, 0, 947, 948, 6, 84, 15, 0, 948, 185, 1, 0, 0, 0, 949, 950, 3, 66, 25, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 85, 16, 0, 952, 953, 6, 85, 12, 0, 953, 187, 1, 0, 0, 0, 954, 955, 3, 64, 24, 0, 955, 956, 1, 0, 0, 0, 956, 957, 6, 86, 11, 0, 957, 189, 1, 0, 0, 0, 958, 959, 3, 60, 22, 0, 959, 960, 1, 0, 0, 0, 960, 961, 6, 87, 11, 0, 961, 191, 1, 0, 0, 0, 962, 963, 3, 62, 23, 0, 963, 964, 1, 0, 0, 0, 964, 965, 6, 88, 11, 0, 965, 193, 1, 0, 0, 0, 966, 967, 3, 66, 25, 0, 967, 968, 1, 0, 0, 0, 968, 969, 6, 89, 16, 0, 969, 970, 6, 89, 12, 0, 970, 195, 1, 0, 0, 0, 971, 972, 3, 168, 76, 0, 972, 973, 1, 0, 0, 0, 973, 974, 6, 90, 14, 0, 974, 197, 1, 0, 0, 0, 975, 976, 3, 170, 77, 0, 976, 977, 1, 0, 0, 0, 977, 978, 6, 91, 17, 0, 978, 199, 1, 0, 0, 0, 979, 980, 3, 334, 159, 0, 980, 981, 1, 0, 0, 0, 981, 982, 6, 92, 18, 0, 982, 201, 1, 0, 0, 0, 983, 984, 3, 104, 44, 0, 984, 985, 1, 0, 0, 0, 985, 986, 6, 93, 19, 0, 986, 203, 1, 0, 0, 0, 987, 988, 3, 100, 42, 0, 988, 989, 1, 0, 0, 0, 989, 990, 6, 94, 20, 0, 990, 205, 1, 0, 0, 0, 991, 992, 7, 16, 0, 0, 992, 993, 7, 3, 0, 0, 993, 994, 7, 5, 0, 0, 994, 995, 7, 12, 0, 0, 995, 996, 7, 0, 0, 0, 996, 997, 7, 12, 0, 0, 997, 998, 7, 5, 0, 0, 998, 999, 7, 12, 0, 0, 999, 207, 1, 0, 0, 0, 1000, 1004, 8, 32, 0, 0, 1001, 1002, 5, 47, 0, 0, 1002, 1004, 8, 33, 0, 0, 1003, 1000, 1, 0, 0, 0, 1003, 1001, 1, 0, 0, 0, 1004, 209, 1, 0, 0, 0, 1005, 1007, 3, 208, 96, 0, 1006, 1005, 1, 0, 0, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1006, 1, 0, 0, 0, 1008, 1009, 1, 0, 0, 0, 1009, 211, 1, 0, 0, 0, 1010, 1011, 3, 210, 97, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 98, 21, 0, 1013, 213, 1, 0, 0, 0, 1014, 1015, 3, 88, 36, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 6, 99, 22, 0, 1017, 215, 1, 0, 0, 0, 1018, 1019, 3, 60, 22, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1021, 6, 100, 11, 0, 1021, 217, 1, 0, 0, 0, 1022, 1023, 3, 62, 23, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1025, 6, 101, 11, 0, 1025, 219, 1, 0, 0, 0, 1026, 1027, 3, 64, 24, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 102, 11, 0, 1029, 221, 1, 0, 0, 0, 1030, 1031, 3, 66, 25, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 103, 16, 0, 1033, 1034, 6, 103, 12, 0, 1034, 223, 1, 0, 0, 0, 1035, 1036, 3, 108, 46, 0, 1036, 1037, 1, 0, 0, 0, 1037, 1038, 6, 104, 23, 0, 1038, 225, 1, 0, 0, 0, 1039, 1040, 3, 104, 44, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 105, 19, 0, 1042, 227, 1, 0, 0, 0, 1043, 1048, 3, 70, 27, 0, 1044, 1048, 3, 68, 26, 0, 1045, 1048, 3, 84, 34, 0, 1046, 1048, 3, 158, 71, 0, 1047, 1043, 1, 0, 0, 0, 1047, 1044, 1, 0, 0, 0, 1047, 1045, 1, 0, 0, 0, 1047, 1046, 1, 0, 0, 0, 1048, 229, 1, 0, 0, 0, 1049, 1052, 3, 70, 27, 0, 1050, 1052, 3, 158, 71, 0, 1051, 1049, 1, 0, 0, 0, 1051, 1050, 1, 0, 0, 0, 1052, 1056, 1, 0, 0, 0, 1053, 1055, 3, 228, 106, 0, 1054, 1053, 1, 0, 0, 0, 1055, 1058, 1, 0, 0, 0, 1056, 1054, 1, 0, 0, 0, 1056, 1057, 1, 0, 0, 0, 1057, 1069, 1, 0, 0, 0, 1058, 1056, 1, 0, 0, 0, 1059, 1062, 3, 84, 34, 0, 1060, 1062, 3, 78, 31, 0, 1061, 1059, 1, 0, 0, 0, 1061, 1060, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1065, 3, 228, 106, 0, 1064, 1063, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 1064, 1, 0, 0, 0, 1066, 1067, 1, 0, 0, 0, 1067, 1069, 1, 0, 0, 0, 1068, 1051, 1, 0, 0, 0, 1068, 1061, 1, 0, 0, 0, 1069, 231, 1, 0, 0, 0, 1070, 1073, 3, 230, 107, 0, 1071, 1073, 3, 174, 79, 0, 1072, 1070, 1, 0, 0, 0, 1072, 1071, 1, 0, 0, 0, 1073, 1074, 1, 0, 0, 0, 1074, 1072, 1, 0, 0, 0, 1074, 1075, 1, 0, 0, 0, 1075, 233, 1, 0, 0, 0, 1076, 1077, 3, 60, 22, 0, 1077, 1078, 1, 0, 0, 0, 1078, 1079, 6, 109, 11, 0, 1079, 235, 1, 0, 0, 0, 1080, 1081, 3, 62, 23, 0, 1081, 1082, 1, 0, 0, 0, 1082, 1083, 6, 110, 11, 0, 1083, 237, 1, 0, 0, 0, 1084, 1085, 3, 64, 24, 0, 1085, 1086, 1, 0, 0, 0, 1086, 1087, 6, 111, 11, 0, 1087, 239, 1, 0, 0, 0, 1088, 1089, 3, 66, 25, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 6, 112, 16, 0, 1091, 1092, 6, 112, 12, 0, 1092, 241, 1, 0, 0, 0, 1093, 1094, 3, 100, 42, 0, 1094, 1095, 1, 0, 0, 0, 1095, 1096, 6, 113, 20, 0, 1096, 243, 1, 0, 0, 0, 1097, 1098, 3, 104, 44, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1100, 6, 114, 19, 0, 1100, 245, 1, 0, 0, 0, 1101, 1102, 3, 108, 46, 0, 1102, 1103, 1, 0, 0, 0, 1103, 1104, 6, 115, 23, 0, 1104, 247, 1, 0, 0, 0, 1105, 1106, 7, 12, 0, 0, 1106, 1107, 7, 2, 0, 0, 1107, 249, 1, 0, 0, 0, 1108, 1109, 3, 232, 108, 0, 1109, 1110, 1, 0, 0, 0, 1110, 1111, 6, 117, 24, 0, 1111, 251, 1, 0, 0, 0, 1112, 1113, 3, 60, 22, 0, 1113, 1114, 1, 0, 0, 0, 1114, 1115, 6, 118, 11, 0, 1115, 253, 1, 0, 0, 0, 1116, 1117, 3, 62, 23, 0, 1117, 1118, 1, 0, 0, 0, 1118, 1119, 6, 119, 11, 0, 1119, 255, 1, 0, 0, 0, 1120, 1121, 3, 64, 24, 0, 1121, 1122, 1, 0, 0, 0, 1122, 1123, 6, 120, 11, 0, 1123, 257, 1, 0, 0, 0, 1124, 1125, 3, 66, 25, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 121, 16, 0, 1127, 1128, 6, 121, 12, 0, 1128, 259, 1, 0, 0, 0, 1129, 1130, 3, 168, 76, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1132, 6, 122, 14, 0, 1132, 1133, 6, 122, 25, 0, 1133, 261, 1, 0, 0, 0, 1134, 1135, 7, 7, 0, 0, 1135, 1136, 7, 9, 0, 0, 1136, 1137, 1, 0, 0, 0, 1137, 1138, 6, 123, 26, 0, 1138, 263, 1, 0, 0, 0, 1139, 1140, 7, 19, 0, 0, 1140, 1141, 7, 1, 0, 0, 1141, 1142, 7, 5, 0, 0, 1142, 1143, 7, 10, 0, 0, 1143, 1144, 1, 0, 0, 0, 1144, 1145, 6, 124, 26, 0, 1145, 265, 1, 0, 0, 0, 1146, 1147, 8, 34, 0, 0, 1147, 267, 1, 0, 0, 0, 1148, 1150, 3, 266, 125, 0, 1149, 1148, 1, 0, 0, 0, 1150, 1151, 1, 0, 0, 0, 1151, 1149, 1, 0, 0, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 1, 0, 0, 0, 1153, 1154, 3, 334, 159, 0, 1154, 1156, 1, 0, 0, 0, 1155, 1149, 1, 0, 0, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1158, 1, 0, 0, 0, 1157, 1159, 3, 266, 125, 0, 1158, 1157, 1, 0, 0, 0, 1159, 1160, 1, 0, 0, 0, 1160, 1158, 1, 0, 0, 0, 1160, 1161, 1, 0, 0, 0, 1161, 269, 1, 0, 0, 0, 1162, 1163, 3, 268, 126, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 6, 127, 27, 0, 1165, 271, 1, 0, 0, 0, 1166, 1167, 3, 60, 22, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 6, 128, 11, 0, 1169, 273, 1, 0, 0, 0, 1170, 1171, 3, 62, 23, 0, 1171, 1172, 1, 0, 0, 0, 1172, 1173, 6, 129, 11, 0, 1173, 275, 1, 0, 0, 0, 1174, 1175, 3, 64, 24, 0, 1175, 1176, 1, 0, 0, 0, 1176, 1177, 6, 130, 11, 0, 1177, 277, 1, 0, 0, 0, 1178, 1179, 3, 66, 25, 0, 1179, 1180, 1, 0, 0, 0, 1180, 1181, 6, 131, 16, 0, 1181, 1182, 6, 131, 12, 0, 1182, 1183, 6, 131, 12, 0, 1183, 279, 1, 0, 0, 0, 1184, 1185, 3, 100, 42, 0, 1185, 1186, 1, 0, 0, 0, 1186, 1187, 6, 132, 20, 0, 1187, 281, 1, 0, 0, 0, 1188, 1189, 3, 104, 44, 0, 1189, 1190, 1, 0, 0, 0, 1190, 1191, 6, 133, 19, 0, 1191, 283, 1, 0, 0, 0, 1192, 1193, 3, 108, 46, 0, 1193, 1194, 1, 0, 0, 0, 1194, 1195, 6, 134, 23, 0, 1195, 285, 1, 0, 0, 0, 1196, 1197, 3, 264, 124, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 135, 28, 0, 1199, 287, 1, 0, 0, 0, 1200, 1201, 3, 232, 108, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 136, 24, 0, 1203, 289, 1, 0, 0, 0, 1204, 1205, 3, 176, 80, 0, 1205, 1206, 1, 0, 0, 0, 1206, 1207, 6, 137, 29, 0, 1207, 291, 1, 0, 0, 0, 1208, 1209, 3, 60, 22, 0, 1209, 1210, 1, 0, 0, 0, 1210, 1211, 6, 138, 11, 0, 1211, 293, 1, 0, 0, 0, 1212, 1213, 3, 62, 23, 0, 1213, 1214, 1, 0, 0, 0, 1214, 1215, 6, 139, 11, 0, 1215, 295, 1, 0, 0, 0, 1216, 1217, 3, 64, 24, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1219, 6, 140, 11, 0, 1219, 297, 1, 0, 0, 0, 1220, 1221, 3, 66, 25, 0, 1221, 1222, 1, 0, 0, 0, 1222, 1223, 6, 141, 16, 0, 1223, 1224, 6, 141, 12, 0, 1224, 299, 1, 0, 0, 0, 1225, 1226, 3, 108, 46, 0, 1226, 1227, 1, 0, 0, 0, 1227, 1228, 6, 142, 23, 0, 1228, 301, 1, 0, 0, 0, 1229, 1230, 3, 176, 80, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1232, 6, 143, 29, 0, 1232, 303, 1, 0, 0, 0, 1233, 1234, 3, 172, 78, 0, 1234, 1235, 1, 0, 0, 0, 1235, 1236, 6, 144, 30, 0, 1236, 305, 1, 0, 0, 0, 1237, 1238, 3, 60, 22, 0, 1238, 1239, 1, 0, 0, 0, 1239, 1240, 6, 145, 11, 0, 1240, 307, 1, 0, 0, 0, 1241, 1242, 3, 62, 23, 0, 1242, 1243, 1, 0, 0, 0, 1243, 1244, 6, 146, 11, 0, 1244, 309, 1, 0, 0, 0, 1245, 1246, 3, 64, 24, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 6, 147, 11, 0, 1248, 311, 1, 0, 0, 0, 1249, 1250, 3, 66, 25, 0, 1250, 1251, 1, 0, 0, 0, 1251, 1252, 6, 148, 16, 0, 1252, 1253, 6, 148, 12, 0, 1253, 313, 1, 0, 0, 0, 1254, 1255, 7, 1, 0, 0, 1255, 1256, 7, 9, 0, 0, 1256, 1257, 7, 15, 0, 0, 1257, 1258, 7, 7, 0, 0, 1258, 315, 1, 0, 0, 0, 1259, 1260, 3, 60, 22, 0, 1260, 1261, 1, 0, 0, 0, 1261, 1262, 6, 150, 11, 0, 1262, 317, 1, 0, 0, 0, 1263, 1264, 3, 62, 23, 0, 1264, 1265, 1, 0, 0, 0, 1265, 1266, 6, 151, 11, 0, 1266, 319, 1, 0, 0, 0, 1267, 1268, 3, 64, 24, 0, 1268, 1269, 1, 0, 0, 0, 1269, 1270, 6, 152, 11, 0, 1270, 321, 1, 0, 0, 0, 1271, 1272, 3, 66, 25, 0, 1272, 1273, 1, 0, 0, 0, 1273, 1274, 6, 153, 16, 0, 1274, 1275, 6, 153, 12, 0, 1275, 323, 1, 0, 0, 0, 1276, 1277, 7, 15, 0, 0, 1277, 1278, 7, 20, 0, 0, 1278, 1279, 7, 9, 0, 0, 1279, 1280, 7, 4, 0, 0, 1280, 1281, 7, 5, 0, 0, 1281, 1282, 7, 1, 0, 0, 1282, 1283, 7, 7, 0, 0, 1283, 1284, 7, 9, 0, 0, 1284, 1285, 7, 2, 0, 0, 1285, 325, 1, 0, 0, 0, 1286, 1287, 3, 60, 22, 0, 1287, 1288, 1, 0, 0, 0, 1288, 1289, 6, 155, 11, 0, 1289, 327, 1, 0, 0, 0, 1290, 1291, 3, 62, 23, 0, 1291, 1292, 1, 0, 0, 0, 1292, 1293, 6, 156, 11, 0, 1293, 329, 1, 0, 0, 0, 1294, 1295, 3, 64, 24, 0, 1295, 1296, 1, 0, 0, 0, 1296, 1297, 6, 157, 11, 0, 1297, 331, 1, 0, 0, 0, 1298, 1299, 3, 170, 77, 0, 1299, 1300, 1, 0, 0, 0, 1300, 1301, 6, 158, 17, 0, 1301, 1302, 6, 158, 12, 0, 1302, 333, 1, 0, 0, 0, 1303, 1304, 5, 58, 0, 0, 1304, 335, 1, 0, 0, 0, 1305, 1311, 3, 78, 31, 0, 1306, 1311, 3, 68, 26, 0, 1307, 1311, 3, 108, 46, 0, 1308, 1311, 3, 70, 27, 0, 1309, 1311, 3, 84, 34, 0, 1310, 1305, 1, 0, 0, 0, 1310, 1306, 1, 0, 0, 0, 1310, 1307, 1, 0, 0, 0, 1310, 1308, 1, 0, 0, 0, 1310, 1309, 1, 0, 0, 0, 1311, 1312, 1, 0, 0, 0, 1312, 1310, 1, 0, 0, 0, 1312, 1313, 1, 0, 0, 0, 1313, 337, 1, 0, 0, 0, 1314, 1315, 3, 60, 22, 0, 1315, 1316, 1, 0, 0, 0, 1316, 1317, 6, 161, 11, 0, 1317, 339, 1, 0, 0, 0, 1318, 1319, 3, 62, 23, 0, 1319, 1320, 1, 0, 0, 0, 1320, 1321, 6, 162, 11, 0, 1321, 341, 1, 0, 0, 0, 1322, 1323, 3, 64, 24, 0, 1323, 1324, 1, 0, 0, 0, 1324, 1325, 6, 163, 11, 0, 1325, 343, 1, 0, 0, 0, 1326, 1327, 3, 66, 25, 0, 1327, 1328, 1, 0, 0, 0, 1328, 1329, 6, 164, 16, 0, 1329, 1330, 6, 164, 12, 0, 1330, 345, 1, 0, 0, 0, 1331, 1332, 3, 334, 159, 0, 1332, 1333, 1, 0, 0, 0, 1333, 1334, 6, 165, 18, 0, 1334, 347, 1, 0, 0, 0, 1335, 1336, 3, 104, 44, 0, 1336, 1337, 1, 0, 0, 0, 1337, 1338, 6, 166, 19, 0, 1338, 349, 1, 0, 0, 0, 1339, 1340, 3, 108, 46, 0, 1340, 1341, 1, 0, 0, 0, 1341, 1342, 6, 167, 23, 0, 1342, 351, 1, 0, 0, 0, 1343, 1344, 3, 262, 123, 0, 1344, 1345, 1, 0, 0, 0, 1345, 1346, 6, 168, 31, 0, 1346, 1347, 6, 168, 32, 0, 1347, 353, 1, 0, 0, 0, 1348, 1349, 3, 210, 97, 0, 1349, 1350, 1, 0, 0, 0, 1350, 1351, 6, 169, 21, 0, 1351, 355, 1, 0, 0, 0, 1352, 1353, 3, 88, 36, 0, 1353, 1354, 1, 0, 0, 0, 1354, 1355, 6, 170, 22, 0, 1355, 357, 1, 0, 0, 0, 1356, 1357, 3, 60, 22, 0, 1357, 1358, 1, 0, 0, 0, 1358, 1359, 6, 171, 11, 0, 1359, 359, 1, 0, 0, 0, 1360, 1361, 3, 62, 23, 0, 1361, 1362, 1, 0, 0, 0, 1362, 1363, 6, 172, 11, 0, 1363, 361, 1, 0, 0, 0, 1364, 1365, 3, 64, 24, 0, 1365, 1366, 1, 0, 0, 0, 1366, 1367, 6, 173, 11, 0, 1367, 363, 1, 0, 0, 0, 1368, 1369, 3, 66, 25, 0, 1369, 1370, 1, 0, 0, 0, 1370, 1371, 6, 174, 16, 0, 1371, 1372, 6, 174, 12, 0, 1372, 1373, 6, 174, 12, 0, 1373, 365, 1, 0, 0, 0, 1374, 1375, 3, 104, 44, 0, 1375, 1376, 1, 0, 0, 0, 1376, 1377, 6, 175, 19, 0, 1377, 367, 1, 0, 0, 0, 1378, 1379, 3, 108, 46, 0, 1379, 1380, 1, 0, 0, 0, 1380, 1381, 6, 176, 23, 0, 1381, 369, 1, 0, 0, 0, 1382, 1383, 3, 232, 108, 0, 1383, 1384, 1, 0, 0, 0, 1384, 1385, 6, 177, 24, 0, 1385, 371, 1, 0, 0, 0, 1386, 1387, 3, 60, 22, 0, 1387, 1388, 1, 0, 0, 0, 1388, 1389, 6, 178, 11, 0, 1389, 373, 1, 0, 0, 0, 1390, 1391, 3, 62, 23, 0, 1391, 1392, 1, 0, 0, 0, 1392, 1393, 6, 179, 11, 0, 1393, 375, 1, 0, 0, 0, 1394, 1395, 3, 64, 24, 0, 1395, 1396, 1, 0, 0, 0, 1396, 1397, 6, 180, 11, 0, 1397, 377, 1, 0, 0, 0, 1398, 1399, 3, 66, 25, 0, 1399, 1400, 1, 0, 0, 0, 1400, 1401, 6, 181, 16, 0, 1401, 1402, 6, 181, 12, 0, 1402, 379, 1, 0, 0, 0, 1403, 1404, 3, 210, 97, 0, 1404, 1405, 1, 0, 0, 0, 1405, 1406, 6, 182, 21, 0, 1406, 1407, 6, 182, 12, 0, 1407, 1408, 6, 182, 33, 0, 1408, 381, 1, 0, 0, 0, 1409, 1410, 3, 88, 36, 0, 1410, 1411, 1, 0, 0, 0, 1411, 1412, 6, 183, 22, 0, 1412, 1413, 6, 183, 12, 0, 1413, 1414, 6, 183, 33, 0, 1414, 383, 1, 0, 0, 0, 1415, 1416, 3, 60, 22, 0, 1416, 1417, 1, 0, 0, 0, 1417, 1418, 6, 184, 11, 0, 1418, 385, 1, 0, 0, 0, 1419, 1420, 3, 62, 23, 0, 1420, 1421, 1, 0, 0, 0, 1421, 1422, 6, 185, 11, 0, 1422, 387, 1, 0, 0, 0, 1423, 1424, 3, 64, 24, 0, 1424, 1425, 1, 0, 0, 0, 1425, 1426, 6, 186, 11, 0, 1426, 389, 1, 0, 0, 0, 1427, 1428, 3, 334, 159, 0, 1428, 1429, 1, 0, 0, 0, 1429, 1430, 6, 187, 18, 0, 1430, 1431, 6, 187, 12, 0, 1431, 1432, 6, 187, 10, 0, 1432, 391, 1, 0, 0, 0, 1433, 1434, 3, 104, 44, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 188, 19, 0, 1436, 1437, 6, 188, 12, 0, 1437, 1438, 6, 188, 10, 0, 1438, 393, 1, 0, 0, 0, 1439, 1440, 3, 60, 22, 0, 1440, 1441, 1, 0, 0, 0, 1441, 1442, 6, 189, 11, 0, 1442, 395, 1, 0, 0, 0, 1443, 1444, 3, 62, 23, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 190, 11, 0, 1446, 397, 1, 0, 0, 0, 1447, 1448, 3, 64, 24, 0, 1448, 1449, 1, 0, 0, 0, 1449, 1450, 6, 191, 11, 0, 1450, 399, 1, 0, 0, 0, 1451, 1452, 3, 176, 80, 0, 1452, 1453, 1, 0, 0, 0, 1453, 1454, 6, 192, 12, 0, 1454, 1455, 6, 192, 0, 0, 1455, 1456, 6, 192, 29, 0, 1456, 401, 1, 0, 0, 0, 1457, 1458, 3, 172, 78, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1460, 6, 193, 12, 0, 1460, 1461, 6, 193, 0, 0, 1461, 1462, 6, 193, 30, 0, 1462, 403, 1, 0, 0, 0, 1463, 1464, 3, 94, 39, 0, 1464, 1465, 1, 0, 0, 0, 1465, 1466, 6, 194, 12, 0, 1466, 1467, 6, 194, 0, 0, 1467, 1468, 6, 194, 34, 0, 1468, 405, 1, 0, 0, 0, 1469, 1470, 3, 66, 25, 0, 1470, 1471, 1, 0, 0, 0, 1471, 1472, 6, 195, 16, 0, 1472, 1473, 6, 195, 12, 0, 1473, 407, 1, 0, 0, 0, 66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 592, 602, 606, 609, 618, 620, 631, 650, 655, 664, 671, 676, 678, 689, 697, 700, 702, 707, 712, 718, 725, 730, 736, 739, 747, 751, 878, 883, 890, 892, 908, 913, 918, 920, 926, 1003, 1008, 1047, 1051, 1056, 1061, 1066, 1068, 1072, 1074, 1151, 1155, 1160, 1310, 1312, 35, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 10, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 12, 0, 5, 14, 0, 0, 1, 0, 4, 0, 0, 7, 20, 0, 7, 66, 0, 5, 0, 0, 7, 26, 0, 7, 67, 0, 7, 109, 0, 7, 35, 0, 7, 33, 0, 7, 77, 0, 7, 27, 0, 7, 37, 0, 7, 81, 0, 5, 11, 0, 5, 7, 0, 7, 91, 0, 7, 90, 0, 7, 69, 0, 7, 68, 0, 7, 89, 0, 5, 13, 0, 5, 15, 0, 7, 30, 0] \ No newline at end of file +[4, 0, 120, 1475, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 4, 20, 587, 8, 20, 11, 20, 12, 20, 588, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 597, 8, 21, 10, 21, 12, 21, 600, 9, 21, 1, 21, 3, 21, 603, 8, 21, 1, 21, 3, 21, 606, 8, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 615, 8, 22, 10, 22, 12, 22, 618, 9, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 4, 23, 626, 8, 23, 11, 23, 12, 23, 627, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 3, 29, 647, 8, 29, 1, 29, 4, 29, 650, 8, 29, 11, 29, 12, 29, 651, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 3, 32, 661, 8, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 668, 8, 34, 1, 35, 1, 35, 1, 35, 5, 35, 673, 8, 35, 10, 35, 12, 35, 676, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 684, 8, 35, 10, 35, 12, 35, 687, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 694, 8, 35, 1, 35, 3, 35, 697, 8, 35, 3, 35, 699, 8, 35, 1, 36, 4, 36, 702, 8, 36, 11, 36, 12, 36, 703, 1, 37, 4, 37, 707, 8, 37, 11, 37, 12, 37, 708, 1, 37, 1, 37, 5, 37, 713, 8, 37, 10, 37, 12, 37, 716, 9, 37, 1, 37, 1, 37, 4, 37, 720, 8, 37, 11, 37, 12, 37, 721, 1, 37, 4, 37, 725, 8, 37, 11, 37, 12, 37, 726, 1, 37, 1, 37, 5, 37, 731, 8, 37, 10, 37, 12, 37, 734, 9, 37, 3, 37, 736, 8, 37, 1, 37, 1, 37, 1, 37, 1, 37, 4, 37, 742, 8, 37, 11, 37, 12, 37, 743, 1, 37, 1, 37, 3, 37, 748, 8, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 3, 74, 875, 8, 74, 1, 74, 5, 74, 878, 8, 74, 10, 74, 12, 74, 881, 9, 74, 1, 74, 1, 74, 4, 74, 885, 8, 74, 11, 74, 12, 74, 886, 3, 74, 889, 8, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 5, 77, 903, 8, 77, 10, 77, 12, 77, 906, 9, 77, 1, 77, 1, 77, 3, 77, 910, 8, 77, 1, 77, 4, 77, 913, 8, 77, 11, 77, 12, 77, 914, 3, 77, 917, 8, 77, 1, 78, 1, 78, 4, 78, 921, 8, 78, 11, 78, 12, 78, 922, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 3, 95, 1000, 8, 95, 1, 96, 4, 96, 1003, 8, 96, 11, 96, 12, 96, 1004, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 3, 107, 1052, 8, 107, 1, 108, 1, 108, 3, 108, 1056, 8, 108, 1, 108, 5, 108, 1059, 8, 108, 10, 108, 12, 108, 1062, 9, 108, 1, 108, 1, 108, 3, 108, 1066, 8, 108, 1, 108, 4, 108, 1069, 8, 108, 11, 108, 12, 108, 1070, 3, 108, 1073, 8, 108, 1, 109, 1, 109, 4, 109, 1077, 8, 109, 11, 109, 12, 109, 1078, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 4, 129, 1162, 8, 129, 11, 129, 12, 129, 1163, 1, 129, 1, 129, 3, 129, 1168, 8, 129, 1, 129, 4, 129, 1171, 8, 129, 11, 129, 12, 129, 1172, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 162, 4, 162, 1312, 8, 162, 11, 162, 12, 162, 1313, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 2, 616, 685, 0, 198, 15, 1, 17, 2, 19, 3, 21, 4, 23, 5, 25, 6, 27, 7, 29, 8, 31, 9, 33, 10, 35, 11, 37, 12, 39, 13, 41, 14, 43, 15, 45, 16, 47, 17, 49, 18, 51, 19, 53, 20, 55, 21, 57, 22, 59, 23, 61, 24, 63, 25, 65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77, 0, 79, 0, 81, 0, 83, 0, 85, 26, 87, 27, 89, 28, 91, 29, 93, 30, 95, 31, 97, 32, 99, 33, 101, 34, 103, 35, 105, 36, 107, 37, 109, 38, 111, 39, 113, 40, 115, 41, 117, 42, 119, 43, 121, 44, 123, 45, 125, 46, 127, 47, 129, 48, 131, 49, 133, 50, 135, 51, 137, 52, 139, 53, 141, 54, 143, 55, 145, 56, 147, 57, 149, 58, 151, 59, 153, 60, 155, 61, 157, 62, 159, 63, 161, 0, 163, 64, 165, 65, 167, 66, 169, 67, 171, 0, 173, 68, 175, 69, 177, 70, 179, 71, 181, 0, 183, 0, 185, 72, 187, 73, 189, 74, 191, 0, 193, 0, 195, 0, 197, 0, 199, 0, 201, 0, 203, 75, 205, 0, 207, 76, 209, 0, 211, 0, 213, 77, 215, 78, 217, 79, 219, 0, 221, 0, 223, 0, 225, 0, 227, 0, 229, 0, 231, 0, 233, 80, 235, 81, 237, 82, 239, 83, 241, 0, 243, 0, 245, 0, 247, 0, 249, 0, 251, 0, 253, 84, 255, 0, 257, 85, 259, 86, 261, 87, 263, 0, 265, 0, 267, 88, 269, 89, 271, 0, 273, 90, 275, 0, 277, 91, 279, 92, 281, 93, 283, 0, 285, 0, 287, 0, 289, 0, 291, 0, 293, 0, 295, 0, 297, 0, 299, 0, 301, 94, 303, 95, 305, 96, 307, 0, 309, 0, 311, 0, 313, 0, 315, 0, 317, 0, 319, 97, 321, 98, 323, 99, 325, 0, 327, 100, 329, 101, 331, 102, 333, 103, 335, 0, 337, 104, 339, 105, 341, 106, 343, 107, 345, 108, 347, 0, 349, 0, 351, 0, 353, 0, 355, 0, 357, 0, 359, 0, 361, 109, 363, 110, 365, 111, 367, 0, 369, 0, 371, 0, 373, 0, 375, 112, 377, 113, 379, 114, 381, 0, 383, 0, 385, 0, 387, 115, 389, 116, 391, 117, 393, 0, 395, 0, 397, 118, 399, 119, 401, 120, 403, 0, 405, 0, 407, 0, 409, 0, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1503, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 1, 63, 1, 0, 0, 0, 1, 85, 1, 0, 0, 0, 1, 87, 1, 0, 0, 0, 1, 89, 1, 0, 0, 0, 1, 91, 1, 0, 0, 0, 1, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 1, 97, 1, 0, 0, 0, 1, 99, 1, 0, 0, 0, 1, 101, 1, 0, 0, 0, 1, 103, 1, 0, 0, 0, 1, 105, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 1, 109, 1, 0, 0, 0, 1, 111, 1, 0, 0, 0, 1, 113, 1, 0, 0, 0, 1, 115, 1, 0, 0, 0, 1, 117, 1, 0, 0, 0, 1, 119, 1, 0, 0, 0, 1, 121, 1, 0, 0, 0, 1, 123, 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 1, 127, 1, 0, 0, 0, 1, 129, 1, 0, 0, 0, 1, 131, 1, 0, 0, 0, 1, 133, 1, 0, 0, 0, 1, 135, 1, 0, 0, 0, 1, 137, 1, 0, 0, 0, 1, 139, 1, 0, 0, 0, 1, 141, 1, 0, 0, 0, 1, 143, 1, 0, 0, 0, 1, 145, 1, 0, 0, 0, 1, 147, 1, 0, 0, 0, 1, 149, 1, 0, 0, 0, 1, 151, 1, 0, 0, 0, 1, 153, 1, 0, 0, 0, 1, 155, 1, 0, 0, 0, 1, 157, 1, 0, 0, 0, 1, 159, 1, 0, 0, 0, 1, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0, 0, 1, 165, 1, 0, 0, 0, 1, 167, 1, 0, 0, 0, 1, 169, 1, 0, 0, 0, 1, 173, 1, 0, 0, 0, 1, 175, 1, 0, 0, 0, 1, 177, 1, 0, 0, 0, 1, 179, 1, 0, 0, 0, 2, 181, 1, 0, 0, 0, 2, 183, 1, 0, 0, 0, 2, 185, 1, 0, 0, 0, 2, 187, 1, 0, 0, 0, 2, 189, 1, 0, 0, 0, 3, 191, 1, 0, 0, 0, 3, 193, 1, 0, 0, 0, 3, 195, 1, 0, 0, 0, 3, 197, 1, 0, 0, 0, 3, 199, 1, 0, 0, 0, 3, 201, 1, 0, 0, 0, 3, 203, 1, 0, 0, 0, 3, 207, 1, 0, 0, 0, 3, 209, 1, 0, 0, 0, 3, 211, 1, 0, 0, 0, 3, 213, 1, 0, 0, 0, 3, 215, 1, 0, 0, 0, 3, 217, 1, 0, 0, 0, 4, 219, 1, 0, 0, 0, 4, 221, 1, 0, 0, 0, 4, 223, 1, 0, 0, 0, 4, 225, 1, 0, 0, 0, 4, 227, 1, 0, 0, 0, 4, 233, 1, 0, 0, 0, 4, 235, 1, 0, 0, 0, 4, 237, 1, 0, 0, 0, 4, 239, 1, 0, 0, 0, 5, 241, 1, 0, 0, 0, 5, 243, 1, 0, 0, 0, 5, 245, 1, 0, 0, 0, 5, 247, 1, 0, 0, 0, 5, 249, 1, 0, 0, 0, 5, 251, 1, 0, 0, 0, 5, 253, 1, 0, 0, 0, 5, 255, 1, 0, 0, 0, 5, 257, 1, 0, 0, 0, 5, 259, 1, 0, 0, 0, 5, 261, 1, 0, 0, 0, 6, 263, 1, 0, 0, 0, 6, 265, 1, 0, 0, 0, 6, 267, 1, 0, 0, 0, 6, 269, 1, 0, 0, 0, 6, 273, 1, 0, 0, 0, 6, 275, 1, 0, 0, 0, 6, 277, 1, 0, 0, 0, 6, 279, 1, 0, 0, 0, 6, 281, 1, 0, 0, 0, 7, 283, 1, 0, 0, 0, 7, 285, 1, 0, 0, 0, 7, 287, 1, 0, 0, 0, 7, 289, 1, 0, 0, 0, 7, 291, 1, 0, 0, 0, 7, 293, 1, 0, 0, 0, 7, 295, 1, 0, 0, 0, 7, 297, 1, 0, 0, 0, 7, 299, 1, 0, 0, 0, 7, 301, 1, 0, 0, 0, 7, 303, 1, 0, 0, 0, 7, 305, 1, 0, 0, 0, 8, 307, 1, 0, 0, 0, 8, 309, 1, 0, 0, 0, 8, 311, 1, 0, 0, 0, 8, 313, 1, 0, 0, 0, 8, 315, 1, 0, 0, 0, 8, 317, 1, 0, 0, 0, 8, 319, 1, 0, 0, 0, 8, 321, 1, 0, 0, 0, 8, 323, 1, 0, 0, 0, 9, 325, 1, 0, 0, 0, 9, 327, 1, 0, 0, 0, 9, 329, 1, 0, 0, 0, 9, 331, 1, 0, 0, 0, 9, 333, 1, 0, 0, 0, 10, 335, 1, 0, 0, 0, 10, 337, 1, 0, 0, 0, 10, 339, 1, 0, 0, 0, 10, 341, 1, 0, 0, 0, 10, 343, 1, 0, 0, 0, 10, 345, 1, 0, 0, 0, 11, 347, 1, 0, 0, 0, 11, 349, 1, 0, 0, 0, 11, 351, 1, 0, 0, 0, 11, 353, 1, 0, 0, 0, 11, 355, 1, 0, 0, 0, 11, 357, 1, 0, 0, 0, 11, 359, 1, 0, 0, 0, 11, 361, 1, 0, 0, 0, 11, 363, 1, 0, 0, 0, 11, 365, 1, 0, 0, 0, 12, 367, 1, 0, 0, 0, 12, 369, 1, 0, 0, 0, 12, 371, 1, 0, 0, 0, 12, 373, 1, 0, 0, 0, 12, 375, 1, 0, 0, 0, 12, 377, 1, 0, 0, 0, 12, 379, 1, 0, 0, 0, 13, 381, 1, 0, 0, 0, 13, 383, 1, 0, 0, 0, 13, 385, 1, 0, 0, 0, 13, 387, 1, 0, 0, 0, 13, 389, 1, 0, 0, 0, 13, 391, 1, 0, 0, 0, 14, 393, 1, 0, 0, 0, 14, 395, 1, 0, 0, 0, 14, 397, 1, 0, 0, 0, 14, 399, 1, 0, 0, 0, 14, 401, 1, 0, 0, 0, 14, 403, 1, 0, 0, 0, 14, 405, 1, 0, 0, 0, 14, 407, 1, 0, 0, 0, 14, 409, 1, 0, 0, 0, 15, 411, 1, 0, 0, 0, 17, 421, 1, 0, 0, 0, 19, 428, 1, 0, 0, 0, 21, 437, 1, 0, 0, 0, 23, 444, 1, 0, 0, 0, 25, 454, 1, 0, 0, 0, 27, 461, 1, 0, 0, 0, 29, 468, 1, 0, 0, 0, 31, 475, 1, 0, 0, 0, 33, 483, 1, 0, 0, 0, 35, 495, 1, 0, 0, 0, 37, 504, 1, 0, 0, 0, 39, 510, 1, 0, 0, 0, 41, 517, 1, 0, 0, 0, 43, 524, 1, 0, 0, 0, 45, 532, 1, 0, 0, 0, 47, 540, 1, 0, 0, 0, 49, 555, 1, 0, 0, 0, 51, 565, 1, 0, 0, 0, 53, 574, 1, 0, 0, 0, 55, 586, 1, 0, 0, 0, 57, 592, 1, 0, 0, 0, 59, 609, 1, 0, 0, 0, 61, 625, 1, 0, 0, 0, 63, 631, 1, 0, 0, 0, 65, 635, 1, 0, 0, 0, 67, 637, 1, 0, 0, 0, 69, 639, 1, 0, 0, 0, 71, 642, 1, 0, 0, 0, 73, 644, 1, 0, 0, 0, 75, 653, 1, 0, 0, 0, 77, 655, 1, 0, 0, 0, 79, 660, 1, 0, 0, 0, 81, 662, 1, 0, 0, 0, 83, 667, 1, 0, 0, 0, 85, 698, 1, 0, 0, 0, 87, 701, 1, 0, 0, 0, 89, 747, 1, 0, 0, 0, 91, 749, 1, 0, 0, 0, 93, 752, 1, 0, 0, 0, 95, 756, 1, 0, 0, 0, 97, 760, 1, 0, 0, 0, 99, 762, 1, 0, 0, 0, 101, 765, 1, 0, 0, 0, 103, 767, 1, 0, 0, 0, 105, 772, 1, 0, 0, 0, 107, 774, 1, 0, 0, 0, 109, 780, 1, 0, 0, 0, 111, 786, 1, 0, 0, 0, 113, 789, 1, 0, 0, 0, 115, 792, 1, 0, 0, 0, 117, 797, 1, 0, 0, 0, 119, 802, 1, 0, 0, 0, 121, 804, 1, 0, 0, 0, 123, 808, 1, 0, 0, 0, 125, 813, 1, 0, 0, 0, 127, 819, 1, 0, 0, 0, 129, 822, 1, 0, 0, 0, 131, 824, 1, 0, 0, 0, 133, 830, 1, 0, 0, 0, 135, 832, 1, 0, 0, 0, 137, 837, 1, 0, 0, 0, 139, 840, 1, 0, 0, 0, 141, 843, 1, 0, 0, 0, 143, 846, 1, 0, 0, 0, 145, 848, 1, 0, 0, 0, 147, 851, 1, 0, 0, 0, 149, 853, 1, 0, 0, 0, 151, 856, 1, 0, 0, 0, 153, 858, 1, 0, 0, 0, 155, 860, 1, 0, 0, 0, 157, 862, 1, 0, 0, 0, 159, 864, 1, 0, 0, 0, 161, 866, 1, 0, 0, 0, 163, 888, 1, 0, 0, 0, 165, 890, 1, 0, 0, 0, 167, 895, 1, 0, 0, 0, 169, 916, 1, 0, 0, 0, 171, 918, 1, 0, 0, 0, 173, 926, 1, 0, 0, 0, 175, 928, 1, 0, 0, 0, 177, 932, 1, 0, 0, 0, 179, 936, 1, 0, 0, 0, 181, 940, 1, 0, 0, 0, 183, 945, 1, 0, 0, 0, 185, 950, 1, 0, 0, 0, 187, 954, 1, 0, 0, 0, 189, 958, 1, 0, 0, 0, 191, 962, 1, 0, 0, 0, 193, 967, 1, 0, 0, 0, 195, 971, 1, 0, 0, 0, 197, 975, 1, 0, 0, 0, 199, 979, 1, 0, 0, 0, 201, 983, 1, 0, 0, 0, 203, 987, 1, 0, 0, 0, 205, 999, 1, 0, 0, 0, 207, 1002, 1, 0, 0, 0, 209, 1006, 1, 0, 0, 0, 211, 1010, 1, 0, 0, 0, 213, 1014, 1, 0, 0, 0, 215, 1018, 1, 0, 0, 0, 217, 1022, 1, 0, 0, 0, 219, 1026, 1, 0, 0, 0, 221, 1031, 1, 0, 0, 0, 223, 1035, 1, 0, 0, 0, 225, 1039, 1, 0, 0, 0, 227, 1043, 1, 0, 0, 0, 229, 1051, 1, 0, 0, 0, 231, 1072, 1, 0, 0, 0, 233, 1076, 1, 0, 0, 0, 235, 1080, 1, 0, 0, 0, 237, 1084, 1, 0, 0, 0, 239, 1088, 1, 0, 0, 0, 241, 1092, 1, 0, 0, 0, 243, 1097, 1, 0, 0, 0, 245, 1101, 1, 0, 0, 0, 247, 1105, 1, 0, 0, 0, 249, 1109, 1, 0, 0, 0, 251, 1113, 1, 0, 0, 0, 253, 1117, 1, 0, 0, 0, 255, 1120, 1, 0, 0, 0, 257, 1124, 1, 0, 0, 0, 259, 1128, 1, 0, 0, 0, 261, 1132, 1, 0, 0, 0, 263, 1136, 1, 0, 0, 0, 265, 1141, 1, 0, 0, 0, 267, 1146, 1, 0, 0, 0, 269, 1151, 1, 0, 0, 0, 271, 1158, 1, 0, 0, 0, 273, 1167, 1, 0, 0, 0, 275, 1174, 1, 0, 0, 0, 277, 1178, 1, 0, 0, 0, 279, 1182, 1, 0, 0, 0, 281, 1186, 1, 0, 0, 0, 283, 1190, 1, 0, 0, 0, 285, 1196, 1, 0, 0, 0, 287, 1200, 1, 0, 0, 0, 289, 1204, 1, 0, 0, 0, 291, 1208, 1, 0, 0, 0, 293, 1212, 1, 0, 0, 0, 295, 1216, 1, 0, 0, 0, 297, 1220, 1, 0, 0, 0, 299, 1224, 1, 0, 0, 0, 301, 1228, 1, 0, 0, 0, 303, 1232, 1, 0, 0, 0, 305, 1236, 1, 0, 0, 0, 307, 1240, 1, 0, 0, 0, 309, 1245, 1, 0, 0, 0, 311, 1249, 1, 0, 0, 0, 313, 1253, 1, 0, 0, 0, 315, 1257, 1, 0, 0, 0, 317, 1261, 1, 0, 0, 0, 319, 1265, 1, 0, 0, 0, 321, 1269, 1, 0, 0, 0, 323, 1273, 1, 0, 0, 0, 325, 1277, 1, 0, 0, 0, 327, 1282, 1, 0, 0, 0, 329, 1287, 1, 0, 0, 0, 331, 1291, 1, 0, 0, 0, 333, 1295, 1, 0, 0, 0, 335, 1299, 1, 0, 0, 0, 337, 1304, 1, 0, 0, 0, 339, 1311, 1, 0, 0, 0, 341, 1315, 1, 0, 0, 0, 343, 1319, 1, 0, 0, 0, 345, 1323, 1, 0, 0, 0, 347, 1327, 1, 0, 0, 0, 349, 1332, 1, 0, 0, 0, 351, 1336, 1, 0, 0, 0, 353, 1340, 1, 0, 0, 0, 355, 1344, 1, 0, 0, 0, 357, 1349, 1, 0, 0, 0, 359, 1353, 1, 0, 0, 0, 361, 1357, 1, 0, 0, 0, 363, 1361, 1, 0, 0, 0, 365, 1365, 1, 0, 0, 0, 367, 1369, 1, 0, 0, 0, 369, 1375, 1, 0, 0, 0, 371, 1379, 1, 0, 0, 0, 373, 1383, 1, 0, 0, 0, 375, 1387, 1, 0, 0, 0, 377, 1391, 1, 0, 0, 0, 379, 1395, 1, 0, 0, 0, 381, 1399, 1, 0, 0, 0, 383, 1404, 1, 0, 0, 0, 385, 1410, 1, 0, 0, 0, 387, 1416, 1, 0, 0, 0, 389, 1420, 1, 0, 0, 0, 391, 1424, 1, 0, 0, 0, 393, 1428, 1, 0, 0, 0, 395, 1434, 1, 0, 0, 0, 397, 1440, 1, 0, 0, 0, 399, 1444, 1, 0, 0, 0, 401, 1448, 1, 0, 0, 0, 403, 1452, 1, 0, 0, 0, 405, 1458, 1, 0, 0, 0, 407, 1464, 1, 0, 0, 0, 409, 1470, 1, 0, 0, 0, 411, 412, 7, 0, 0, 0, 412, 413, 7, 1, 0, 0, 413, 414, 7, 2, 0, 0, 414, 415, 7, 2, 0, 0, 415, 416, 7, 3, 0, 0, 416, 417, 7, 4, 0, 0, 417, 418, 7, 5, 0, 0, 418, 419, 1, 0, 0, 0, 419, 420, 6, 0, 0, 0, 420, 16, 1, 0, 0, 0, 421, 422, 7, 0, 0, 0, 422, 423, 7, 6, 0, 0, 423, 424, 7, 7, 0, 0, 424, 425, 7, 8, 0, 0, 425, 426, 1, 0, 0, 0, 426, 427, 6, 1, 1, 0, 427, 18, 1, 0, 0, 0, 428, 429, 7, 3, 0, 0, 429, 430, 7, 9, 0, 0, 430, 431, 7, 6, 0, 0, 431, 432, 7, 1, 0, 0, 432, 433, 7, 4, 0, 0, 433, 434, 7, 10, 0, 0, 434, 435, 1, 0, 0, 0, 435, 436, 6, 2, 2, 0, 436, 20, 1, 0, 0, 0, 437, 438, 7, 3, 0, 0, 438, 439, 7, 11, 0, 0, 439, 440, 7, 12, 0, 0, 440, 441, 7, 13, 0, 0, 441, 442, 1, 0, 0, 0, 442, 443, 6, 3, 0, 0, 443, 22, 1, 0, 0, 0, 444, 445, 7, 3, 0, 0, 445, 446, 7, 14, 0, 0, 446, 447, 7, 8, 0, 0, 447, 448, 7, 13, 0, 0, 448, 449, 7, 12, 0, 0, 449, 450, 7, 1, 0, 0, 450, 451, 7, 9, 0, 0, 451, 452, 1, 0, 0, 0, 452, 453, 6, 4, 3, 0, 453, 24, 1, 0, 0, 0, 454, 455, 7, 15, 0, 0, 455, 456, 7, 6, 0, 0, 456, 457, 7, 7, 0, 0, 457, 458, 7, 16, 0, 0, 458, 459, 1, 0, 0, 0, 459, 460, 6, 5, 4, 0, 460, 26, 1, 0, 0, 0, 461, 462, 7, 17, 0, 0, 462, 463, 7, 6, 0, 0, 463, 464, 7, 7, 0, 0, 464, 465, 7, 18, 0, 0, 465, 466, 1, 0, 0, 0, 466, 467, 6, 6, 0, 0, 467, 28, 1, 0, 0, 0, 468, 469, 7, 18, 0, 0, 469, 470, 7, 3, 0, 0, 470, 471, 7, 3, 0, 0, 471, 472, 7, 8, 0, 0, 472, 473, 1, 0, 0, 0, 473, 474, 6, 7, 1, 0, 474, 30, 1, 0, 0, 0, 475, 476, 7, 13, 0, 0, 476, 477, 7, 1, 0, 0, 477, 478, 7, 16, 0, 0, 478, 479, 7, 1, 0, 0, 479, 480, 7, 5, 0, 0, 480, 481, 1, 0, 0, 0, 481, 482, 6, 8, 0, 0, 482, 32, 1, 0, 0, 0, 483, 484, 7, 16, 0, 0, 484, 485, 7, 11, 0, 0, 485, 486, 5, 95, 0, 0, 486, 487, 7, 3, 0, 0, 487, 488, 7, 14, 0, 0, 488, 489, 7, 8, 0, 0, 489, 490, 7, 12, 0, 0, 490, 491, 7, 9, 0, 0, 491, 492, 7, 0, 0, 0, 492, 493, 1, 0, 0, 0, 493, 494, 6, 9, 5, 0, 494, 34, 1, 0, 0, 0, 495, 496, 7, 6, 0, 0, 496, 497, 7, 3, 0, 0, 497, 498, 7, 9, 0, 0, 498, 499, 7, 12, 0, 0, 499, 500, 7, 16, 0, 0, 500, 501, 7, 3, 0, 0, 501, 502, 1, 0, 0, 0, 502, 503, 6, 10, 6, 0, 503, 36, 1, 0, 0, 0, 504, 505, 7, 6, 0, 0, 505, 506, 7, 7, 0, 0, 506, 507, 7, 19, 0, 0, 507, 508, 1, 0, 0, 0, 508, 509, 6, 11, 0, 0, 509, 38, 1, 0, 0, 0, 510, 511, 7, 2, 0, 0, 511, 512, 7, 10, 0, 0, 512, 513, 7, 7, 0, 0, 513, 514, 7, 19, 0, 0, 514, 515, 1, 0, 0, 0, 515, 516, 6, 12, 7, 0, 516, 40, 1, 0, 0, 0, 517, 518, 7, 2, 0, 0, 518, 519, 7, 7, 0, 0, 519, 520, 7, 6, 0, 0, 520, 521, 7, 5, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 6, 13, 0, 0, 523, 42, 1, 0, 0, 0, 524, 525, 7, 2, 0, 0, 525, 526, 7, 5, 0, 0, 526, 527, 7, 12, 0, 0, 527, 528, 7, 5, 0, 0, 528, 529, 7, 2, 0, 0, 529, 530, 1, 0, 0, 0, 530, 531, 6, 14, 0, 0, 531, 44, 1, 0, 0, 0, 532, 533, 7, 19, 0, 0, 533, 534, 7, 10, 0, 0, 534, 535, 7, 3, 0, 0, 535, 536, 7, 6, 0, 0, 536, 537, 7, 3, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 6, 15, 0, 0, 539, 46, 1, 0, 0, 0, 540, 541, 4, 16, 0, 0, 541, 542, 7, 1, 0, 0, 542, 543, 7, 9, 0, 0, 543, 544, 7, 13, 0, 0, 544, 545, 7, 1, 0, 0, 545, 546, 7, 9, 0, 0, 546, 547, 7, 3, 0, 0, 547, 548, 7, 2, 0, 0, 548, 549, 7, 5, 0, 0, 549, 550, 7, 12, 0, 0, 550, 551, 7, 5, 0, 0, 551, 552, 7, 2, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 6, 16, 0, 0, 554, 48, 1, 0, 0, 0, 555, 556, 4, 17, 1, 0, 556, 557, 7, 13, 0, 0, 557, 558, 7, 7, 0, 0, 558, 559, 7, 7, 0, 0, 559, 560, 7, 18, 0, 0, 560, 561, 7, 20, 0, 0, 561, 562, 7, 8, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 6, 17, 8, 0, 564, 50, 1, 0, 0, 0, 565, 566, 4, 18, 2, 0, 566, 567, 7, 16, 0, 0, 567, 568, 7, 12, 0, 0, 568, 569, 7, 5, 0, 0, 569, 570, 7, 4, 0, 0, 570, 571, 7, 10, 0, 0, 571, 572, 1, 0, 0, 0, 572, 573, 6, 18, 0, 0, 573, 52, 1, 0, 0, 0, 574, 575, 4, 19, 3, 0, 575, 576, 7, 16, 0, 0, 576, 577, 7, 3, 0, 0, 577, 578, 7, 5, 0, 0, 578, 579, 7, 6, 0, 0, 579, 580, 7, 1, 0, 0, 580, 581, 7, 4, 0, 0, 581, 582, 7, 2, 0, 0, 582, 583, 1, 0, 0, 0, 583, 584, 6, 19, 9, 0, 584, 54, 1, 0, 0, 0, 585, 587, 8, 21, 0, 0, 586, 585, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 586, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 6, 20, 0, 0, 591, 56, 1, 0, 0, 0, 592, 593, 5, 47, 0, 0, 593, 594, 5, 47, 0, 0, 594, 598, 1, 0, 0, 0, 595, 597, 8, 22, 0, 0, 596, 595, 1, 0, 0, 0, 597, 600, 1, 0, 0, 0, 598, 596, 1, 0, 0, 0, 598, 599, 1, 0, 0, 0, 599, 602, 1, 0, 0, 0, 600, 598, 1, 0, 0, 0, 601, 603, 5, 13, 0, 0, 602, 601, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, 605, 1, 0, 0, 0, 604, 606, 5, 10, 0, 0, 605, 604, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 6, 21, 10, 0, 608, 58, 1, 0, 0, 0, 609, 610, 5, 47, 0, 0, 610, 611, 5, 42, 0, 0, 611, 616, 1, 0, 0, 0, 612, 615, 3, 59, 22, 0, 613, 615, 9, 0, 0, 0, 614, 612, 1, 0, 0, 0, 614, 613, 1, 0, 0, 0, 615, 618, 1, 0, 0, 0, 616, 617, 1, 0, 0, 0, 616, 614, 1, 0, 0, 0, 617, 619, 1, 0, 0, 0, 618, 616, 1, 0, 0, 0, 619, 620, 5, 42, 0, 0, 620, 621, 5, 47, 0, 0, 621, 622, 1, 0, 0, 0, 622, 623, 6, 22, 10, 0, 623, 60, 1, 0, 0, 0, 624, 626, 7, 23, 0, 0, 625, 624, 1, 0, 0, 0, 626, 627, 1, 0, 0, 0, 627, 625, 1, 0, 0, 0, 627, 628, 1, 0, 0, 0, 628, 629, 1, 0, 0, 0, 629, 630, 6, 23, 10, 0, 630, 62, 1, 0, 0, 0, 631, 632, 5, 124, 0, 0, 632, 633, 1, 0, 0, 0, 633, 634, 6, 24, 11, 0, 634, 64, 1, 0, 0, 0, 635, 636, 7, 24, 0, 0, 636, 66, 1, 0, 0, 0, 637, 638, 7, 25, 0, 0, 638, 68, 1, 0, 0, 0, 639, 640, 5, 92, 0, 0, 640, 641, 7, 26, 0, 0, 641, 70, 1, 0, 0, 0, 642, 643, 8, 27, 0, 0, 643, 72, 1, 0, 0, 0, 644, 646, 7, 3, 0, 0, 645, 647, 7, 28, 0, 0, 646, 645, 1, 0, 0, 0, 646, 647, 1, 0, 0, 0, 647, 649, 1, 0, 0, 0, 648, 650, 3, 65, 25, 0, 649, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 651, 652, 1, 0, 0, 0, 652, 74, 1, 0, 0, 0, 653, 654, 5, 64, 0, 0, 654, 76, 1, 0, 0, 0, 655, 656, 5, 96, 0, 0, 656, 78, 1, 0, 0, 0, 657, 661, 8, 29, 0, 0, 658, 659, 5, 96, 0, 0, 659, 661, 5, 96, 0, 0, 660, 657, 1, 0, 0, 0, 660, 658, 1, 0, 0, 0, 661, 80, 1, 0, 0, 0, 662, 663, 5, 95, 0, 0, 663, 82, 1, 0, 0, 0, 664, 668, 3, 67, 26, 0, 665, 668, 3, 65, 25, 0, 666, 668, 3, 81, 33, 0, 667, 664, 1, 0, 0, 0, 667, 665, 1, 0, 0, 0, 667, 666, 1, 0, 0, 0, 668, 84, 1, 0, 0, 0, 669, 674, 5, 34, 0, 0, 670, 673, 3, 69, 27, 0, 671, 673, 3, 71, 28, 0, 672, 670, 1, 0, 0, 0, 672, 671, 1, 0, 0, 0, 673, 676, 1, 0, 0, 0, 674, 672, 1, 0, 0, 0, 674, 675, 1, 0, 0, 0, 675, 677, 1, 0, 0, 0, 676, 674, 1, 0, 0, 0, 677, 699, 5, 34, 0, 0, 678, 679, 5, 34, 0, 0, 679, 680, 5, 34, 0, 0, 680, 681, 5, 34, 0, 0, 681, 685, 1, 0, 0, 0, 682, 684, 8, 22, 0, 0, 683, 682, 1, 0, 0, 0, 684, 687, 1, 0, 0, 0, 685, 686, 1, 0, 0, 0, 685, 683, 1, 0, 0, 0, 686, 688, 1, 0, 0, 0, 687, 685, 1, 0, 0, 0, 688, 689, 5, 34, 0, 0, 689, 690, 5, 34, 0, 0, 690, 691, 5, 34, 0, 0, 691, 693, 1, 0, 0, 0, 692, 694, 5, 34, 0, 0, 693, 692, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 694, 696, 1, 0, 0, 0, 695, 697, 5, 34, 0, 0, 696, 695, 1, 0, 0, 0, 696, 697, 1, 0, 0, 0, 697, 699, 1, 0, 0, 0, 698, 669, 1, 0, 0, 0, 698, 678, 1, 0, 0, 0, 699, 86, 1, 0, 0, 0, 700, 702, 3, 65, 25, 0, 701, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 701, 1, 0, 0, 0, 703, 704, 1, 0, 0, 0, 704, 88, 1, 0, 0, 0, 705, 707, 3, 65, 25, 0, 706, 705, 1, 0, 0, 0, 707, 708, 1, 0, 0, 0, 708, 706, 1, 0, 0, 0, 708, 709, 1, 0, 0, 0, 709, 710, 1, 0, 0, 0, 710, 714, 3, 105, 45, 0, 711, 713, 3, 65, 25, 0, 712, 711, 1, 0, 0, 0, 713, 716, 1, 0, 0, 0, 714, 712, 1, 0, 0, 0, 714, 715, 1, 0, 0, 0, 715, 748, 1, 0, 0, 0, 716, 714, 1, 0, 0, 0, 717, 719, 3, 105, 45, 0, 718, 720, 3, 65, 25, 0, 719, 718, 1, 0, 0, 0, 720, 721, 1, 0, 0, 0, 721, 719, 1, 0, 0, 0, 721, 722, 1, 0, 0, 0, 722, 748, 1, 0, 0, 0, 723, 725, 3, 65, 25, 0, 724, 723, 1, 0, 0, 0, 725, 726, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 735, 1, 0, 0, 0, 728, 732, 3, 105, 45, 0, 729, 731, 3, 65, 25, 0, 730, 729, 1, 0, 0, 0, 731, 734, 1, 0, 0, 0, 732, 730, 1, 0, 0, 0, 732, 733, 1, 0, 0, 0, 733, 736, 1, 0, 0, 0, 734, 732, 1, 0, 0, 0, 735, 728, 1, 0, 0, 0, 735, 736, 1, 0, 0, 0, 736, 737, 1, 0, 0, 0, 737, 738, 3, 73, 29, 0, 738, 748, 1, 0, 0, 0, 739, 741, 3, 105, 45, 0, 740, 742, 3, 65, 25, 0, 741, 740, 1, 0, 0, 0, 742, 743, 1, 0, 0, 0, 743, 741, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 3, 73, 29, 0, 746, 748, 1, 0, 0, 0, 747, 706, 1, 0, 0, 0, 747, 717, 1, 0, 0, 0, 747, 724, 1, 0, 0, 0, 747, 739, 1, 0, 0, 0, 748, 90, 1, 0, 0, 0, 749, 750, 7, 30, 0, 0, 750, 751, 7, 31, 0, 0, 751, 92, 1, 0, 0, 0, 752, 753, 7, 12, 0, 0, 753, 754, 7, 9, 0, 0, 754, 755, 7, 0, 0, 0, 755, 94, 1, 0, 0, 0, 756, 757, 7, 12, 0, 0, 757, 758, 7, 2, 0, 0, 758, 759, 7, 4, 0, 0, 759, 96, 1, 0, 0, 0, 760, 761, 5, 61, 0, 0, 761, 98, 1, 0, 0, 0, 762, 763, 5, 58, 0, 0, 763, 764, 5, 58, 0, 0, 764, 100, 1, 0, 0, 0, 765, 766, 5, 44, 0, 0, 766, 102, 1, 0, 0, 0, 767, 768, 7, 0, 0, 0, 768, 769, 7, 3, 0, 0, 769, 770, 7, 2, 0, 0, 770, 771, 7, 4, 0, 0, 771, 104, 1, 0, 0, 0, 772, 773, 5, 46, 0, 0, 773, 106, 1, 0, 0, 0, 774, 775, 7, 15, 0, 0, 775, 776, 7, 12, 0, 0, 776, 777, 7, 13, 0, 0, 777, 778, 7, 2, 0, 0, 778, 779, 7, 3, 0, 0, 779, 108, 1, 0, 0, 0, 780, 781, 7, 15, 0, 0, 781, 782, 7, 1, 0, 0, 782, 783, 7, 6, 0, 0, 783, 784, 7, 2, 0, 0, 784, 785, 7, 5, 0, 0, 785, 110, 1, 0, 0, 0, 786, 787, 7, 1, 0, 0, 787, 788, 7, 9, 0, 0, 788, 112, 1, 0, 0, 0, 789, 790, 7, 1, 0, 0, 790, 791, 7, 2, 0, 0, 791, 114, 1, 0, 0, 0, 792, 793, 7, 13, 0, 0, 793, 794, 7, 12, 0, 0, 794, 795, 7, 2, 0, 0, 795, 796, 7, 5, 0, 0, 796, 116, 1, 0, 0, 0, 797, 798, 7, 13, 0, 0, 798, 799, 7, 1, 0, 0, 799, 800, 7, 18, 0, 0, 800, 801, 7, 3, 0, 0, 801, 118, 1, 0, 0, 0, 802, 803, 5, 40, 0, 0, 803, 120, 1, 0, 0, 0, 804, 805, 7, 9, 0, 0, 805, 806, 7, 7, 0, 0, 806, 807, 7, 5, 0, 0, 807, 122, 1, 0, 0, 0, 808, 809, 7, 9, 0, 0, 809, 810, 7, 20, 0, 0, 810, 811, 7, 13, 0, 0, 811, 812, 7, 13, 0, 0, 812, 124, 1, 0, 0, 0, 813, 814, 7, 9, 0, 0, 814, 815, 7, 20, 0, 0, 815, 816, 7, 13, 0, 0, 816, 817, 7, 13, 0, 0, 817, 818, 7, 2, 0, 0, 818, 126, 1, 0, 0, 0, 819, 820, 7, 7, 0, 0, 820, 821, 7, 6, 0, 0, 821, 128, 1, 0, 0, 0, 822, 823, 5, 63, 0, 0, 823, 130, 1, 0, 0, 0, 824, 825, 7, 6, 0, 0, 825, 826, 7, 13, 0, 0, 826, 827, 7, 1, 0, 0, 827, 828, 7, 18, 0, 0, 828, 829, 7, 3, 0, 0, 829, 132, 1, 0, 0, 0, 830, 831, 5, 41, 0, 0, 831, 134, 1, 0, 0, 0, 832, 833, 7, 5, 0, 0, 833, 834, 7, 6, 0, 0, 834, 835, 7, 20, 0, 0, 835, 836, 7, 3, 0, 0, 836, 136, 1, 0, 0, 0, 837, 838, 5, 61, 0, 0, 838, 839, 5, 61, 0, 0, 839, 138, 1, 0, 0, 0, 840, 841, 5, 61, 0, 0, 841, 842, 5, 126, 0, 0, 842, 140, 1, 0, 0, 0, 843, 844, 5, 33, 0, 0, 844, 845, 5, 61, 0, 0, 845, 142, 1, 0, 0, 0, 846, 847, 5, 60, 0, 0, 847, 144, 1, 0, 0, 0, 848, 849, 5, 60, 0, 0, 849, 850, 5, 61, 0, 0, 850, 146, 1, 0, 0, 0, 851, 852, 5, 62, 0, 0, 852, 148, 1, 0, 0, 0, 853, 854, 5, 62, 0, 0, 854, 855, 5, 61, 0, 0, 855, 150, 1, 0, 0, 0, 856, 857, 5, 43, 0, 0, 857, 152, 1, 0, 0, 0, 858, 859, 5, 45, 0, 0, 859, 154, 1, 0, 0, 0, 860, 861, 5, 42, 0, 0, 861, 156, 1, 0, 0, 0, 862, 863, 5, 47, 0, 0, 863, 158, 1, 0, 0, 0, 864, 865, 5, 37, 0, 0, 865, 160, 1, 0, 0, 0, 866, 867, 4, 73, 4, 0, 867, 868, 3, 51, 18, 0, 868, 869, 1, 0, 0, 0, 869, 870, 6, 73, 12, 0, 870, 162, 1, 0, 0, 0, 871, 874, 3, 129, 57, 0, 872, 875, 3, 67, 26, 0, 873, 875, 3, 81, 33, 0, 874, 872, 1, 0, 0, 0, 874, 873, 1, 0, 0, 0, 875, 879, 1, 0, 0, 0, 876, 878, 3, 83, 34, 0, 877, 876, 1, 0, 0, 0, 878, 881, 1, 0, 0, 0, 879, 877, 1, 0, 0, 0, 879, 880, 1, 0, 0, 0, 880, 889, 1, 0, 0, 0, 881, 879, 1, 0, 0, 0, 882, 884, 3, 129, 57, 0, 883, 885, 3, 65, 25, 0, 884, 883, 1, 0, 0, 0, 885, 886, 1, 0, 0, 0, 886, 884, 1, 0, 0, 0, 886, 887, 1, 0, 0, 0, 887, 889, 1, 0, 0, 0, 888, 871, 1, 0, 0, 0, 888, 882, 1, 0, 0, 0, 889, 164, 1, 0, 0, 0, 890, 891, 5, 91, 0, 0, 891, 892, 1, 0, 0, 0, 892, 893, 6, 75, 0, 0, 893, 894, 6, 75, 0, 0, 894, 166, 1, 0, 0, 0, 895, 896, 5, 93, 0, 0, 896, 897, 1, 0, 0, 0, 897, 898, 6, 76, 11, 0, 898, 899, 6, 76, 11, 0, 899, 168, 1, 0, 0, 0, 900, 904, 3, 67, 26, 0, 901, 903, 3, 83, 34, 0, 902, 901, 1, 0, 0, 0, 903, 906, 1, 0, 0, 0, 904, 902, 1, 0, 0, 0, 904, 905, 1, 0, 0, 0, 905, 917, 1, 0, 0, 0, 906, 904, 1, 0, 0, 0, 907, 910, 3, 81, 33, 0, 908, 910, 3, 75, 30, 0, 909, 907, 1, 0, 0, 0, 909, 908, 1, 0, 0, 0, 910, 912, 1, 0, 0, 0, 911, 913, 3, 83, 34, 0, 912, 911, 1, 0, 0, 0, 913, 914, 1, 0, 0, 0, 914, 912, 1, 0, 0, 0, 914, 915, 1, 0, 0, 0, 915, 917, 1, 0, 0, 0, 916, 900, 1, 0, 0, 0, 916, 909, 1, 0, 0, 0, 917, 170, 1, 0, 0, 0, 918, 920, 3, 77, 31, 0, 919, 921, 3, 79, 32, 0, 920, 919, 1, 0, 0, 0, 921, 922, 1, 0, 0, 0, 922, 920, 1, 0, 0, 0, 922, 923, 1, 0, 0, 0, 923, 924, 1, 0, 0, 0, 924, 925, 3, 77, 31, 0, 925, 172, 1, 0, 0, 0, 926, 927, 3, 171, 78, 0, 927, 174, 1, 0, 0, 0, 928, 929, 3, 57, 21, 0, 929, 930, 1, 0, 0, 0, 930, 931, 6, 80, 10, 0, 931, 176, 1, 0, 0, 0, 932, 933, 3, 59, 22, 0, 933, 934, 1, 0, 0, 0, 934, 935, 6, 81, 10, 0, 935, 178, 1, 0, 0, 0, 936, 937, 3, 61, 23, 0, 937, 938, 1, 0, 0, 0, 938, 939, 6, 82, 10, 0, 939, 180, 1, 0, 0, 0, 940, 941, 3, 165, 75, 0, 941, 942, 1, 0, 0, 0, 942, 943, 6, 83, 13, 0, 943, 944, 6, 83, 14, 0, 944, 182, 1, 0, 0, 0, 945, 946, 3, 63, 24, 0, 946, 947, 1, 0, 0, 0, 947, 948, 6, 84, 15, 0, 948, 949, 6, 84, 11, 0, 949, 184, 1, 0, 0, 0, 950, 951, 3, 61, 23, 0, 951, 952, 1, 0, 0, 0, 952, 953, 6, 85, 10, 0, 953, 186, 1, 0, 0, 0, 954, 955, 3, 57, 21, 0, 955, 956, 1, 0, 0, 0, 956, 957, 6, 86, 10, 0, 957, 188, 1, 0, 0, 0, 958, 959, 3, 59, 22, 0, 959, 960, 1, 0, 0, 0, 960, 961, 6, 87, 10, 0, 961, 190, 1, 0, 0, 0, 962, 963, 3, 63, 24, 0, 963, 964, 1, 0, 0, 0, 964, 965, 6, 88, 15, 0, 965, 966, 6, 88, 11, 0, 966, 192, 1, 0, 0, 0, 967, 968, 3, 165, 75, 0, 968, 969, 1, 0, 0, 0, 969, 970, 6, 89, 13, 0, 970, 194, 1, 0, 0, 0, 971, 972, 3, 167, 76, 0, 972, 973, 1, 0, 0, 0, 973, 974, 6, 90, 16, 0, 974, 196, 1, 0, 0, 0, 975, 976, 3, 337, 161, 0, 976, 977, 1, 0, 0, 0, 977, 978, 6, 91, 17, 0, 978, 198, 1, 0, 0, 0, 979, 980, 3, 101, 43, 0, 980, 981, 1, 0, 0, 0, 981, 982, 6, 92, 18, 0, 982, 200, 1, 0, 0, 0, 983, 984, 3, 97, 41, 0, 984, 985, 1, 0, 0, 0, 985, 986, 6, 93, 19, 0, 986, 202, 1, 0, 0, 0, 987, 988, 7, 16, 0, 0, 988, 989, 7, 3, 0, 0, 989, 990, 7, 5, 0, 0, 990, 991, 7, 12, 0, 0, 991, 992, 7, 0, 0, 0, 992, 993, 7, 12, 0, 0, 993, 994, 7, 5, 0, 0, 994, 995, 7, 12, 0, 0, 995, 204, 1, 0, 0, 0, 996, 1000, 8, 32, 0, 0, 997, 998, 5, 47, 0, 0, 998, 1000, 8, 33, 0, 0, 999, 996, 1, 0, 0, 0, 999, 997, 1, 0, 0, 0, 1000, 206, 1, 0, 0, 0, 1001, 1003, 3, 205, 95, 0, 1002, 1001, 1, 0, 0, 0, 1003, 1004, 1, 0, 0, 0, 1004, 1002, 1, 0, 0, 0, 1004, 1005, 1, 0, 0, 0, 1005, 208, 1, 0, 0, 0, 1006, 1007, 3, 207, 96, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1009, 6, 97, 20, 0, 1009, 210, 1, 0, 0, 0, 1010, 1011, 3, 85, 35, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 98, 21, 0, 1013, 212, 1, 0, 0, 0, 1014, 1015, 3, 57, 21, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 6, 99, 10, 0, 1017, 214, 1, 0, 0, 0, 1018, 1019, 3, 59, 22, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1021, 6, 100, 10, 0, 1021, 216, 1, 0, 0, 0, 1022, 1023, 3, 61, 23, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1025, 6, 101, 10, 0, 1025, 218, 1, 0, 0, 0, 1026, 1027, 3, 63, 24, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 102, 15, 0, 1029, 1030, 6, 102, 11, 0, 1030, 220, 1, 0, 0, 0, 1031, 1032, 3, 105, 45, 0, 1032, 1033, 1, 0, 0, 0, 1033, 1034, 6, 103, 22, 0, 1034, 222, 1, 0, 0, 0, 1035, 1036, 3, 101, 43, 0, 1036, 1037, 1, 0, 0, 0, 1037, 1038, 6, 104, 18, 0, 1038, 224, 1, 0, 0, 0, 1039, 1040, 3, 129, 57, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 105, 23, 0, 1042, 226, 1, 0, 0, 0, 1043, 1044, 3, 163, 74, 0, 1044, 1045, 1, 0, 0, 0, 1045, 1046, 6, 106, 24, 0, 1046, 228, 1, 0, 0, 0, 1047, 1052, 3, 67, 26, 0, 1048, 1052, 3, 65, 25, 0, 1049, 1052, 3, 81, 33, 0, 1050, 1052, 3, 155, 70, 0, 1051, 1047, 1, 0, 0, 0, 1051, 1048, 1, 0, 0, 0, 1051, 1049, 1, 0, 0, 0, 1051, 1050, 1, 0, 0, 0, 1052, 230, 1, 0, 0, 0, 1053, 1056, 3, 67, 26, 0, 1054, 1056, 3, 155, 70, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1054, 1, 0, 0, 0, 1056, 1060, 1, 0, 0, 0, 1057, 1059, 3, 229, 107, 0, 1058, 1057, 1, 0, 0, 0, 1059, 1062, 1, 0, 0, 0, 1060, 1058, 1, 0, 0, 0, 1060, 1061, 1, 0, 0, 0, 1061, 1073, 1, 0, 0, 0, 1062, 1060, 1, 0, 0, 0, 1063, 1066, 3, 81, 33, 0, 1064, 1066, 3, 75, 30, 0, 1065, 1063, 1, 0, 0, 0, 1065, 1064, 1, 0, 0, 0, 1066, 1068, 1, 0, 0, 0, 1067, 1069, 3, 229, 107, 0, 1068, 1067, 1, 0, 0, 0, 1069, 1070, 1, 0, 0, 0, 1070, 1068, 1, 0, 0, 0, 1070, 1071, 1, 0, 0, 0, 1071, 1073, 1, 0, 0, 0, 1072, 1055, 1, 0, 0, 0, 1072, 1065, 1, 0, 0, 0, 1073, 232, 1, 0, 0, 0, 1074, 1077, 3, 231, 108, 0, 1075, 1077, 3, 171, 78, 0, 1076, 1074, 1, 0, 0, 0, 1076, 1075, 1, 0, 0, 0, 1077, 1078, 1, 0, 0, 0, 1078, 1076, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 234, 1, 0, 0, 0, 1080, 1081, 3, 57, 21, 0, 1081, 1082, 1, 0, 0, 0, 1082, 1083, 6, 110, 10, 0, 1083, 236, 1, 0, 0, 0, 1084, 1085, 3, 59, 22, 0, 1085, 1086, 1, 0, 0, 0, 1086, 1087, 6, 111, 10, 0, 1087, 238, 1, 0, 0, 0, 1088, 1089, 3, 61, 23, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 6, 112, 10, 0, 1091, 240, 1, 0, 0, 0, 1092, 1093, 3, 63, 24, 0, 1093, 1094, 1, 0, 0, 0, 1094, 1095, 6, 113, 15, 0, 1095, 1096, 6, 113, 11, 0, 1096, 242, 1, 0, 0, 0, 1097, 1098, 3, 97, 41, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1100, 6, 114, 19, 0, 1100, 244, 1, 0, 0, 0, 1101, 1102, 3, 101, 43, 0, 1102, 1103, 1, 0, 0, 0, 1103, 1104, 6, 115, 18, 0, 1104, 246, 1, 0, 0, 0, 1105, 1106, 3, 105, 45, 0, 1106, 1107, 1, 0, 0, 0, 1107, 1108, 6, 116, 22, 0, 1108, 248, 1, 0, 0, 0, 1109, 1110, 3, 129, 57, 0, 1110, 1111, 1, 0, 0, 0, 1111, 1112, 6, 117, 23, 0, 1112, 250, 1, 0, 0, 0, 1113, 1114, 3, 163, 74, 0, 1114, 1115, 1, 0, 0, 0, 1115, 1116, 6, 118, 24, 0, 1116, 252, 1, 0, 0, 0, 1117, 1118, 7, 12, 0, 0, 1118, 1119, 7, 2, 0, 0, 1119, 254, 1, 0, 0, 0, 1120, 1121, 3, 233, 109, 0, 1121, 1122, 1, 0, 0, 0, 1122, 1123, 6, 120, 25, 0, 1123, 256, 1, 0, 0, 0, 1124, 1125, 3, 57, 21, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 121, 10, 0, 1127, 258, 1, 0, 0, 0, 1128, 1129, 3, 59, 22, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1131, 6, 122, 10, 0, 1131, 260, 1, 0, 0, 0, 1132, 1133, 3, 61, 23, 0, 1133, 1134, 1, 0, 0, 0, 1134, 1135, 6, 123, 10, 0, 1135, 262, 1, 0, 0, 0, 1136, 1137, 3, 63, 24, 0, 1137, 1138, 1, 0, 0, 0, 1138, 1139, 6, 124, 15, 0, 1139, 1140, 6, 124, 11, 0, 1140, 264, 1, 0, 0, 0, 1141, 1142, 3, 165, 75, 0, 1142, 1143, 1, 0, 0, 0, 1143, 1144, 6, 125, 13, 0, 1144, 1145, 6, 125, 26, 0, 1145, 266, 1, 0, 0, 0, 1146, 1147, 7, 7, 0, 0, 1147, 1148, 7, 9, 0, 0, 1148, 1149, 1, 0, 0, 0, 1149, 1150, 6, 126, 27, 0, 1150, 268, 1, 0, 0, 0, 1151, 1152, 7, 19, 0, 0, 1152, 1153, 7, 1, 0, 0, 1153, 1154, 7, 5, 0, 0, 1154, 1155, 7, 10, 0, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1157, 6, 127, 27, 0, 1157, 270, 1, 0, 0, 0, 1158, 1159, 8, 34, 0, 0, 1159, 272, 1, 0, 0, 0, 1160, 1162, 3, 271, 128, 0, 1161, 1160, 1, 0, 0, 0, 1162, 1163, 1, 0, 0, 0, 1163, 1161, 1, 0, 0, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 1, 0, 0, 0, 1165, 1166, 3, 337, 161, 0, 1166, 1168, 1, 0, 0, 0, 1167, 1161, 1, 0, 0, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1170, 1, 0, 0, 0, 1169, 1171, 3, 271, 128, 0, 1170, 1169, 1, 0, 0, 0, 1171, 1172, 1, 0, 0, 0, 1172, 1170, 1, 0, 0, 0, 1172, 1173, 1, 0, 0, 0, 1173, 274, 1, 0, 0, 0, 1174, 1175, 3, 273, 129, 0, 1175, 1176, 1, 0, 0, 0, 1176, 1177, 6, 130, 28, 0, 1177, 276, 1, 0, 0, 0, 1178, 1179, 3, 57, 21, 0, 1179, 1180, 1, 0, 0, 0, 1180, 1181, 6, 131, 10, 0, 1181, 278, 1, 0, 0, 0, 1182, 1183, 3, 59, 22, 0, 1183, 1184, 1, 0, 0, 0, 1184, 1185, 6, 132, 10, 0, 1185, 280, 1, 0, 0, 0, 1186, 1187, 3, 61, 23, 0, 1187, 1188, 1, 0, 0, 0, 1188, 1189, 6, 133, 10, 0, 1189, 282, 1, 0, 0, 0, 1190, 1191, 3, 63, 24, 0, 1191, 1192, 1, 0, 0, 0, 1192, 1193, 6, 134, 15, 0, 1193, 1194, 6, 134, 11, 0, 1194, 1195, 6, 134, 11, 0, 1195, 284, 1, 0, 0, 0, 1196, 1197, 3, 97, 41, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 135, 19, 0, 1199, 286, 1, 0, 0, 0, 1200, 1201, 3, 101, 43, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 136, 18, 0, 1203, 288, 1, 0, 0, 0, 1204, 1205, 3, 105, 45, 0, 1205, 1206, 1, 0, 0, 0, 1206, 1207, 6, 137, 22, 0, 1207, 290, 1, 0, 0, 0, 1208, 1209, 3, 269, 127, 0, 1209, 1210, 1, 0, 0, 0, 1210, 1211, 6, 138, 29, 0, 1211, 292, 1, 0, 0, 0, 1212, 1213, 3, 233, 109, 0, 1213, 1214, 1, 0, 0, 0, 1214, 1215, 6, 139, 25, 0, 1215, 294, 1, 0, 0, 0, 1216, 1217, 3, 173, 79, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1219, 6, 140, 30, 0, 1219, 296, 1, 0, 0, 0, 1220, 1221, 3, 129, 57, 0, 1221, 1222, 1, 0, 0, 0, 1222, 1223, 6, 141, 23, 0, 1223, 298, 1, 0, 0, 0, 1224, 1225, 3, 163, 74, 0, 1225, 1226, 1, 0, 0, 0, 1226, 1227, 6, 142, 24, 0, 1227, 300, 1, 0, 0, 0, 1228, 1229, 3, 57, 21, 0, 1229, 1230, 1, 0, 0, 0, 1230, 1231, 6, 143, 10, 0, 1231, 302, 1, 0, 0, 0, 1232, 1233, 3, 59, 22, 0, 1233, 1234, 1, 0, 0, 0, 1234, 1235, 6, 144, 10, 0, 1235, 304, 1, 0, 0, 0, 1236, 1237, 3, 61, 23, 0, 1237, 1238, 1, 0, 0, 0, 1238, 1239, 6, 145, 10, 0, 1239, 306, 1, 0, 0, 0, 1240, 1241, 3, 63, 24, 0, 1241, 1242, 1, 0, 0, 0, 1242, 1243, 6, 146, 15, 0, 1243, 1244, 6, 146, 11, 0, 1244, 308, 1, 0, 0, 0, 1245, 1246, 3, 105, 45, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 6, 147, 22, 0, 1248, 310, 1, 0, 0, 0, 1249, 1250, 3, 129, 57, 0, 1250, 1251, 1, 0, 0, 0, 1251, 1252, 6, 148, 23, 0, 1252, 312, 1, 0, 0, 0, 1253, 1254, 3, 163, 74, 0, 1254, 1255, 1, 0, 0, 0, 1255, 1256, 6, 149, 24, 0, 1256, 314, 1, 0, 0, 0, 1257, 1258, 3, 173, 79, 0, 1258, 1259, 1, 0, 0, 0, 1259, 1260, 6, 150, 30, 0, 1260, 316, 1, 0, 0, 0, 1261, 1262, 3, 169, 77, 0, 1262, 1263, 1, 0, 0, 0, 1263, 1264, 6, 151, 31, 0, 1264, 318, 1, 0, 0, 0, 1265, 1266, 3, 57, 21, 0, 1266, 1267, 1, 0, 0, 0, 1267, 1268, 6, 152, 10, 0, 1268, 320, 1, 0, 0, 0, 1269, 1270, 3, 59, 22, 0, 1270, 1271, 1, 0, 0, 0, 1271, 1272, 6, 153, 10, 0, 1272, 322, 1, 0, 0, 0, 1273, 1274, 3, 61, 23, 0, 1274, 1275, 1, 0, 0, 0, 1275, 1276, 6, 154, 10, 0, 1276, 324, 1, 0, 0, 0, 1277, 1278, 3, 63, 24, 0, 1278, 1279, 1, 0, 0, 0, 1279, 1280, 6, 155, 15, 0, 1280, 1281, 6, 155, 11, 0, 1281, 326, 1, 0, 0, 0, 1282, 1283, 7, 1, 0, 0, 1283, 1284, 7, 9, 0, 0, 1284, 1285, 7, 15, 0, 0, 1285, 1286, 7, 7, 0, 0, 1286, 328, 1, 0, 0, 0, 1287, 1288, 3, 57, 21, 0, 1288, 1289, 1, 0, 0, 0, 1289, 1290, 6, 157, 10, 0, 1290, 330, 1, 0, 0, 0, 1291, 1292, 3, 59, 22, 0, 1292, 1293, 1, 0, 0, 0, 1293, 1294, 6, 158, 10, 0, 1294, 332, 1, 0, 0, 0, 1295, 1296, 3, 61, 23, 0, 1296, 1297, 1, 0, 0, 0, 1297, 1298, 6, 159, 10, 0, 1298, 334, 1, 0, 0, 0, 1299, 1300, 3, 167, 76, 0, 1300, 1301, 1, 0, 0, 0, 1301, 1302, 6, 160, 16, 0, 1302, 1303, 6, 160, 11, 0, 1303, 336, 1, 0, 0, 0, 1304, 1305, 5, 58, 0, 0, 1305, 338, 1, 0, 0, 0, 1306, 1312, 3, 75, 30, 0, 1307, 1312, 3, 65, 25, 0, 1308, 1312, 3, 105, 45, 0, 1309, 1312, 3, 67, 26, 0, 1310, 1312, 3, 81, 33, 0, 1311, 1306, 1, 0, 0, 0, 1311, 1307, 1, 0, 0, 0, 1311, 1308, 1, 0, 0, 0, 1311, 1309, 1, 0, 0, 0, 1311, 1310, 1, 0, 0, 0, 1312, 1313, 1, 0, 0, 0, 1313, 1311, 1, 0, 0, 0, 1313, 1314, 1, 0, 0, 0, 1314, 340, 1, 0, 0, 0, 1315, 1316, 3, 57, 21, 0, 1316, 1317, 1, 0, 0, 0, 1317, 1318, 6, 163, 10, 0, 1318, 342, 1, 0, 0, 0, 1319, 1320, 3, 59, 22, 0, 1320, 1321, 1, 0, 0, 0, 1321, 1322, 6, 164, 10, 0, 1322, 344, 1, 0, 0, 0, 1323, 1324, 3, 61, 23, 0, 1324, 1325, 1, 0, 0, 0, 1325, 1326, 6, 165, 10, 0, 1326, 346, 1, 0, 0, 0, 1327, 1328, 3, 63, 24, 0, 1328, 1329, 1, 0, 0, 0, 1329, 1330, 6, 166, 15, 0, 1330, 1331, 6, 166, 11, 0, 1331, 348, 1, 0, 0, 0, 1332, 1333, 3, 337, 161, 0, 1333, 1334, 1, 0, 0, 0, 1334, 1335, 6, 167, 17, 0, 1335, 350, 1, 0, 0, 0, 1336, 1337, 3, 101, 43, 0, 1337, 1338, 1, 0, 0, 0, 1338, 1339, 6, 168, 18, 0, 1339, 352, 1, 0, 0, 0, 1340, 1341, 3, 105, 45, 0, 1341, 1342, 1, 0, 0, 0, 1342, 1343, 6, 169, 22, 0, 1343, 354, 1, 0, 0, 0, 1344, 1345, 3, 267, 126, 0, 1345, 1346, 1, 0, 0, 0, 1346, 1347, 6, 170, 32, 0, 1347, 1348, 6, 170, 33, 0, 1348, 356, 1, 0, 0, 0, 1349, 1350, 3, 207, 96, 0, 1350, 1351, 1, 0, 0, 0, 1351, 1352, 6, 171, 20, 0, 1352, 358, 1, 0, 0, 0, 1353, 1354, 3, 85, 35, 0, 1354, 1355, 1, 0, 0, 0, 1355, 1356, 6, 172, 21, 0, 1356, 360, 1, 0, 0, 0, 1357, 1358, 3, 57, 21, 0, 1358, 1359, 1, 0, 0, 0, 1359, 1360, 6, 173, 10, 0, 1360, 362, 1, 0, 0, 0, 1361, 1362, 3, 59, 22, 0, 1362, 1363, 1, 0, 0, 0, 1363, 1364, 6, 174, 10, 0, 1364, 364, 1, 0, 0, 0, 1365, 1366, 3, 61, 23, 0, 1366, 1367, 1, 0, 0, 0, 1367, 1368, 6, 175, 10, 0, 1368, 366, 1, 0, 0, 0, 1369, 1370, 3, 63, 24, 0, 1370, 1371, 1, 0, 0, 0, 1371, 1372, 6, 176, 15, 0, 1372, 1373, 6, 176, 11, 0, 1373, 1374, 6, 176, 11, 0, 1374, 368, 1, 0, 0, 0, 1375, 1376, 3, 101, 43, 0, 1376, 1377, 1, 0, 0, 0, 1377, 1378, 6, 177, 18, 0, 1378, 370, 1, 0, 0, 0, 1379, 1380, 3, 105, 45, 0, 1380, 1381, 1, 0, 0, 0, 1381, 1382, 6, 178, 22, 0, 1382, 372, 1, 0, 0, 0, 1383, 1384, 3, 233, 109, 0, 1384, 1385, 1, 0, 0, 0, 1385, 1386, 6, 179, 25, 0, 1386, 374, 1, 0, 0, 0, 1387, 1388, 3, 57, 21, 0, 1388, 1389, 1, 0, 0, 0, 1389, 1390, 6, 180, 10, 0, 1390, 376, 1, 0, 0, 0, 1391, 1392, 3, 59, 22, 0, 1392, 1393, 1, 0, 0, 0, 1393, 1394, 6, 181, 10, 0, 1394, 378, 1, 0, 0, 0, 1395, 1396, 3, 61, 23, 0, 1396, 1397, 1, 0, 0, 0, 1397, 1398, 6, 182, 10, 0, 1398, 380, 1, 0, 0, 0, 1399, 1400, 3, 63, 24, 0, 1400, 1401, 1, 0, 0, 0, 1401, 1402, 6, 183, 15, 0, 1402, 1403, 6, 183, 11, 0, 1403, 382, 1, 0, 0, 0, 1404, 1405, 3, 207, 96, 0, 1405, 1406, 1, 0, 0, 0, 1406, 1407, 6, 184, 20, 0, 1407, 1408, 6, 184, 11, 0, 1408, 1409, 6, 184, 34, 0, 1409, 384, 1, 0, 0, 0, 1410, 1411, 3, 85, 35, 0, 1411, 1412, 1, 0, 0, 0, 1412, 1413, 6, 185, 21, 0, 1413, 1414, 6, 185, 11, 0, 1414, 1415, 6, 185, 34, 0, 1415, 386, 1, 0, 0, 0, 1416, 1417, 3, 57, 21, 0, 1417, 1418, 1, 0, 0, 0, 1418, 1419, 6, 186, 10, 0, 1419, 388, 1, 0, 0, 0, 1420, 1421, 3, 59, 22, 0, 1421, 1422, 1, 0, 0, 0, 1422, 1423, 6, 187, 10, 0, 1423, 390, 1, 0, 0, 0, 1424, 1425, 3, 61, 23, 0, 1425, 1426, 1, 0, 0, 0, 1426, 1427, 6, 188, 10, 0, 1427, 392, 1, 0, 0, 0, 1428, 1429, 3, 337, 161, 0, 1429, 1430, 1, 0, 0, 0, 1430, 1431, 6, 189, 17, 0, 1431, 1432, 6, 189, 11, 0, 1432, 1433, 6, 189, 9, 0, 1433, 394, 1, 0, 0, 0, 1434, 1435, 3, 101, 43, 0, 1435, 1436, 1, 0, 0, 0, 1436, 1437, 6, 190, 18, 0, 1437, 1438, 6, 190, 11, 0, 1438, 1439, 6, 190, 9, 0, 1439, 396, 1, 0, 0, 0, 1440, 1441, 3, 57, 21, 0, 1441, 1442, 1, 0, 0, 0, 1442, 1443, 6, 191, 10, 0, 1443, 398, 1, 0, 0, 0, 1444, 1445, 3, 59, 22, 0, 1445, 1446, 1, 0, 0, 0, 1446, 1447, 6, 192, 10, 0, 1447, 400, 1, 0, 0, 0, 1448, 1449, 3, 61, 23, 0, 1449, 1450, 1, 0, 0, 0, 1450, 1451, 6, 193, 10, 0, 1451, 402, 1, 0, 0, 0, 1452, 1453, 3, 173, 79, 0, 1453, 1454, 1, 0, 0, 0, 1454, 1455, 6, 194, 11, 0, 1455, 1456, 6, 194, 0, 0, 1456, 1457, 6, 194, 30, 0, 1457, 404, 1, 0, 0, 0, 1458, 1459, 3, 169, 77, 0, 1459, 1460, 1, 0, 0, 0, 1460, 1461, 6, 195, 11, 0, 1461, 1462, 6, 195, 0, 0, 1462, 1463, 6, 195, 31, 0, 1463, 406, 1, 0, 0, 0, 1464, 1465, 3, 91, 38, 0, 1465, 1466, 1, 0, 0, 0, 1466, 1467, 6, 196, 11, 0, 1467, 1468, 6, 196, 0, 0, 1468, 1469, 6, 196, 35, 0, 1469, 408, 1, 0, 0, 0, 1470, 1471, 3, 63, 24, 0, 1471, 1472, 1, 0, 0, 0, 1472, 1473, 6, 197, 15, 0, 1473, 1474, 6, 197, 11, 0, 1474, 410, 1, 0, 0, 0, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 588, 598, 602, 605, 614, 616, 627, 646, 651, 660, 667, 672, 674, 685, 693, 696, 698, 703, 708, 714, 721, 726, 732, 735, 743, 747, 874, 879, 886, 888, 904, 909, 914, 916, 922, 999, 1004, 1051, 1055, 1060, 1065, 1070, 1072, 1076, 1078, 1163, 1167, 1172, 1311, 1313, 36, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 11, 0, 5, 13, 0, 0, 1, 0, 4, 0, 0, 7, 19, 0, 7, 65, 0, 5, 0, 0, 7, 25, 0, 7, 66, 0, 7, 104, 0, 7, 34, 0, 7, 32, 0, 7, 76, 0, 7, 26, 0, 7, 36, 0, 7, 48, 0, 7, 64, 0, 7, 80, 0, 5, 10, 0, 5, 7, 0, 7, 90, 0, 7, 89, 0, 7, 68, 0, 7, 67, 0, 7, 88, 0, 5, 12, 0, 5, 14, 0, 7, 29, 0] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens index 747fbbc64cf5f..4fd37ab9900f2 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens @@ -7,122 +7,117 @@ FROM=6 GROK=7 KEEP=8 LIMIT=9 -META=10 -MV_EXPAND=11 -RENAME=12 -ROW=13 -SHOW=14 -SORT=15 -STATS=16 -WHERE=17 -DEV_INLINESTATS=18 -DEV_LOOKUP=19 -DEV_MATCH=20 -DEV_METRICS=21 -UNKNOWN_CMD=22 -LINE_COMMENT=23 -MULTILINE_COMMENT=24 -WS=25 -PIPE=26 -QUOTED_STRING=27 -INTEGER_LITERAL=28 -DECIMAL_LITERAL=29 -BY=30 -AND=31 -ASC=32 -ASSIGN=33 -CAST_OP=34 -COMMA=35 -DESC=36 -DOT=37 -FALSE=38 -FIRST=39 -IN=40 -IS=41 -LAST=42 -LIKE=43 -LP=44 -NOT=45 -NULL=46 -NULLS=47 -OR=48 -PARAM=49 -RLIKE=50 -RP=51 -TRUE=52 -EQ=53 -CIEQ=54 -NEQ=55 -LT=56 -LTE=57 -GT=58 -GTE=59 -PLUS=60 -MINUS=61 -ASTERISK=62 -SLASH=63 -PERCENT=64 -NAMED_OR_POSITIONAL_PARAM=65 -OPENING_BRACKET=66 -CLOSING_BRACKET=67 -UNQUOTED_IDENTIFIER=68 -QUOTED_IDENTIFIER=69 -EXPR_LINE_COMMENT=70 -EXPR_MULTILINE_COMMENT=71 -EXPR_WS=72 -EXPLAIN_WS=73 -EXPLAIN_LINE_COMMENT=74 -EXPLAIN_MULTILINE_COMMENT=75 -METADATA=76 -UNQUOTED_SOURCE=77 -FROM_LINE_COMMENT=78 -FROM_MULTILINE_COMMENT=79 -FROM_WS=80 -ID_PATTERN=81 -PROJECT_LINE_COMMENT=82 -PROJECT_MULTILINE_COMMENT=83 -PROJECT_WS=84 -AS=85 -RENAME_LINE_COMMENT=86 -RENAME_MULTILINE_COMMENT=87 -RENAME_WS=88 -ON=89 -WITH=90 -ENRICH_POLICY_NAME=91 -ENRICH_LINE_COMMENT=92 -ENRICH_MULTILINE_COMMENT=93 -ENRICH_WS=94 -ENRICH_FIELD_LINE_COMMENT=95 -ENRICH_FIELD_MULTILINE_COMMENT=96 -ENRICH_FIELD_WS=97 -MVEXPAND_LINE_COMMENT=98 -MVEXPAND_MULTILINE_COMMENT=99 -MVEXPAND_WS=100 -INFO=101 -SHOW_LINE_COMMENT=102 -SHOW_MULTILINE_COMMENT=103 -SHOW_WS=104 -FUNCTIONS=105 -META_LINE_COMMENT=106 -META_MULTILINE_COMMENT=107 -META_WS=108 -COLON=109 -SETTING=110 -SETTING_LINE_COMMENT=111 -SETTTING_MULTILINE_COMMENT=112 -SETTING_WS=113 -LOOKUP_LINE_COMMENT=114 -LOOKUP_MULTILINE_COMMENT=115 -LOOKUP_WS=116 -LOOKUP_FIELD_LINE_COMMENT=117 -LOOKUP_FIELD_MULTILINE_COMMENT=118 -LOOKUP_FIELD_WS=119 -METRICS_LINE_COMMENT=120 -METRICS_MULTILINE_COMMENT=121 -METRICS_WS=122 -CLOSING_METRICS_LINE_COMMENT=123 -CLOSING_METRICS_MULTILINE_COMMENT=124 -CLOSING_METRICS_WS=125 +MV_EXPAND=10 +RENAME=11 +ROW=12 +SHOW=13 +SORT=14 +STATS=15 +WHERE=16 +DEV_INLINESTATS=17 +DEV_LOOKUP=18 +DEV_MATCH=19 +DEV_METRICS=20 +UNKNOWN_CMD=21 +LINE_COMMENT=22 +MULTILINE_COMMENT=23 +WS=24 +PIPE=25 +QUOTED_STRING=26 +INTEGER_LITERAL=27 +DECIMAL_LITERAL=28 +BY=29 +AND=30 +ASC=31 +ASSIGN=32 +CAST_OP=33 +COMMA=34 +DESC=35 +DOT=36 +FALSE=37 +FIRST=38 +IN=39 +IS=40 +LAST=41 +LIKE=42 +LP=43 +NOT=44 +NULL=45 +NULLS=46 +OR=47 +PARAM=48 +RLIKE=49 +RP=50 +TRUE=51 +EQ=52 +CIEQ=53 +NEQ=54 +LT=55 +LTE=56 +GT=57 +GTE=58 +PLUS=59 +MINUS=60 +ASTERISK=61 +SLASH=62 +PERCENT=63 +NAMED_OR_POSITIONAL_PARAM=64 +OPENING_BRACKET=65 +CLOSING_BRACKET=66 +UNQUOTED_IDENTIFIER=67 +QUOTED_IDENTIFIER=68 +EXPR_LINE_COMMENT=69 +EXPR_MULTILINE_COMMENT=70 +EXPR_WS=71 +EXPLAIN_WS=72 +EXPLAIN_LINE_COMMENT=73 +EXPLAIN_MULTILINE_COMMENT=74 +METADATA=75 +UNQUOTED_SOURCE=76 +FROM_LINE_COMMENT=77 +FROM_MULTILINE_COMMENT=78 +FROM_WS=79 +ID_PATTERN=80 +PROJECT_LINE_COMMENT=81 +PROJECT_MULTILINE_COMMENT=82 +PROJECT_WS=83 +AS=84 +RENAME_LINE_COMMENT=85 +RENAME_MULTILINE_COMMENT=86 +RENAME_WS=87 +ON=88 +WITH=89 +ENRICH_POLICY_NAME=90 +ENRICH_LINE_COMMENT=91 +ENRICH_MULTILINE_COMMENT=92 +ENRICH_WS=93 +ENRICH_FIELD_LINE_COMMENT=94 +ENRICH_FIELD_MULTILINE_COMMENT=95 +ENRICH_FIELD_WS=96 +MVEXPAND_LINE_COMMENT=97 +MVEXPAND_MULTILINE_COMMENT=98 +MVEXPAND_WS=99 +INFO=100 +SHOW_LINE_COMMENT=101 +SHOW_MULTILINE_COMMENT=102 +SHOW_WS=103 +COLON=104 +SETTING=105 +SETTING_LINE_COMMENT=106 +SETTTING_MULTILINE_COMMENT=107 +SETTING_WS=108 +LOOKUP_LINE_COMMENT=109 +LOOKUP_MULTILINE_COMMENT=110 +LOOKUP_WS=111 +LOOKUP_FIELD_LINE_COMMENT=112 +LOOKUP_FIELD_MULTILINE_COMMENT=113 +LOOKUP_FIELD_WS=114 +METRICS_LINE_COMMENT=115 +METRICS_MULTILINE_COMMENT=116 +METRICS_WS=117 +CLOSING_METRICS_LINE_COMMENT=118 +CLOSING_METRICS_MULTILINE_COMMENT=119 +CLOSING_METRICS_WS=120 'dissect'=1 'drop'=2 'enrich'=3 @@ -132,55 +127,53 @@ CLOSING_METRICS_WS=125 'grok'=7 'keep'=8 'limit'=9 -'meta'=10 -'mv_expand'=11 -'rename'=12 -'row'=13 -'show'=14 -'sort'=15 -'stats'=16 -'where'=17 -'|'=26 -'by'=30 -'and'=31 -'asc'=32 -'='=33 -'::'=34 -','=35 -'desc'=36 -'.'=37 -'false'=38 -'first'=39 -'in'=40 -'is'=41 -'last'=42 -'like'=43 -'('=44 -'not'=45 -'null'=46 -'nulls'=47 -'or'=48 -'?'=49 -'rlike'=50 -')'=51 -'true'=52 -'=='=53 -'=~'=54 -'!='=55 -'<'=56 -'<='=57 -'>'=58 -'>='=59 -'+'=60 -'-'=61 -'*'=62 -'/'=63 -'%'=64 -']'=67 -'metadata'=76 -'as'=85 -'on'=89 -'with'=90 -'info'=101 -'functions'=105 -':'=109 +'mv_expand'=10 +'rename'=11 +'row'=12 +'show'=13 +'sort'=14 +'stats'=15 +'where'=16 +'|'=25 +'by'=29 +'and'=30 +'asc'=31 +'='=32 +'::'=33 +','=34 +'desc'=35 +'.'=36 +'false'=37 +'first'=38 +'in'=39 +'is'=40 +'last'=41 +'like'=42 +'('=43 +'not'=44 +'null'=45 +'nulls'=46 +'or'=47 +'?'=48 +'rlike'=49 +')'=50 +'true'=51 +'=='=52 +'=~'=53 +'!='=54 +'<'=55 +'<='=56 +'>'=57 +'>='=58 +'+'=59 +'-'=60 +'*'=61 +'/'=62 +'%'=63 +']'=66 +'metadata'=75 +'as'=84 +'on'=88 +'with'=89 +'info'=100 +':'=104 diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts index a3be12402651c..bbd8286b61d71 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts @@ -32,122 +32,117 @@ export default class esql_lexer extends lexer_config { public static readonly GROK = 7; public static readonly KEEP = 8; public static readonly LIMIT = 9; - public static readonly META = 10; - public static readonly MV_EXPAND = 11; - public static readonly RENAME = 12; - public static readonly ROW = 13; - public static readonly SHOW = 14; - public static readonly SORT = 15; - public static readonly STATS = 16; - public static readonly WHERE = 17; - public static readonly DEV_INLINESTATS = 18; - public static readonly DEV_LOOKUP = 19; - public static readonly DEV_MATCH = 20; - public static readonly DEV_METRICS = 21; - public static readonly UNKNOWN_CMD = 22; - public static readonly LINE_COMMENT = 23; - public static readonly MULTILINE_COMMENT = 24; - public static readonly WS = 25; - public static readonly PIPE = 26; - public static readonly QUOTED_STRING = 27; - public static readonly INTEGER_LITERAL = 28; - public static readonly DECIMAL_LITERAL = 29; - public static readonly BY = 30; - public static readonly AND = 31; - public static readonly ASC = 32; - public static readonly ASSIGN = 33; - public static readonly CAST_OP = 34; - public static readonly COMMA = 35; - public static readonly DESC = 36; - public static readonly DOT = 37; - public static readonly FALSE = 38; - public static readonly FIRST = 39; - public static readonly IN = 40; - public static readonly IS = 41; - public static readonly LAST = 42; - public static readonly LIKE = 43; - public static readonly LP = 44; - public static readonly NOT = 45; - public static readonly NULL = 46; - public static readonly NULLS = 47; - public static readonly OR = 48; - public static readonly PARAM = 49; - public static readonly RLIKE = 50; - public static readonly RP = 51; - public static readonly TRUE = 52; - public static readonly EQ = 53; - public static readonly CIEQ = 54; - public static readonly NEQ = 55; - public static readonly LT = 56; - public static readonly LTE = 57; - public static readonly GT = 58; - public static readonly GTE = 59; - public static readonly PLUS = 60; - public static readonly MINUS = 61; - public static readonly ASTERISK = 62; - public static readonly SLASH = 63; - public static readonly PERCENT = 64; - public static readonly NAMED_OR_POSITIONAL_PARAM = 65; - public static readonly OPENING_BRACKET = 66; - public static readonly CLOSING_BRACKET = 67; - public static readonly UNQUOTED_IDENTIFIER = 68; - public static readonly QUOTED_IDENTIFIER = 69; - public static readonly EXPR_LINE_COMMENT = 70; - public static readonly EXPR_MULTILINE_COMMENT = 71; - public static readonly EXPR_WS = 72; - public static readonly EXPLAIN_WS = 73; - public static readonly EXPLAIN_LINE_COMMENT = 74; - public static readonly EXPLAIN_MULTILINE_COMMENT = 75; - public static readonly METADATA = 76; - public static readonly UNQUOTED_SOURCE = 77; - public static readonly FROM_LINE_COMMENT = 78; - public static readonly FROM_MULTILINE_COMMENT = 79; - public static readonly FROM_WS = 80; - public static readonly ID_PATTERN = 81; - public static readonly PROJECT_LINE_COMMENT = 82; - public static readonly PROJECT_MULTILINE_COMMENT = 83; - public static readonly PROJECT_WS = 84; - public static readonly AS = 85; - public static readonly RENAME_LINE_COMMENT = 86; - public static readonly RENAME_MULTILINE_COMMENT = 87; - public static readonly RENAME_WS = 88; - public static readonly ON = 89; - public static readonly WITH = 90; - public static readonly ENRICH_POLICY_NAME = 91; - public static readonly ENRICH_LINE_COMMENT = 92; - public static readonly ENRICH_MULTILINE_COMMENT = 93; - public static readonly ENRICH_WS = 94; - public static readonly ENRICH_FIELD_LINE_COMMENT = 95; - public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 96; - public static readonly ENRICH_FIELD_WS = 97; - public static readonly MVEXPAND_LINE_COMMENT = 98; - public static readonly MVEXPAND_MULTILINE_COMMENT = 99; - public static readonly MVEXPAND_WS = 100; - public static readonly INFO = 101; - public static readonly SHOW_LINE_COMMENT = 102; - public static readonly SHOW_MULTILINE_COMMENT = 103; - public static readonly SHOW_WS = 104; - public static readonly FUNCTIONS = 105; - public static readonly META_LINE_COMMENT = 106; - public static readonly META_MULTILINE_COMMENT = 107; - public static readonly META_WS = 108; - public static readonly COLON = 109; - public static readonly SETTING = 110; - public static readonly SETTING_LINE_COMMENT = 111; - public static readonly SETTTING_MULTILINE_COMMENT = 112; - public static readonly SETTING_WS = 113; - public static readonly LOOKUP_LINE_COMMENT = 114; - public static readonly LOOKUP_MULTILINE_COMMENT = 115; - public static readonly LOOKUP_WS = 116; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 117; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 118; - public static readonly LOOKUP_FIELD_WS = 119; - public static readonly METRICS_LINE_COMMENT = 120; - public static readonly METRICS_MULTILINE_COMMENT = 121; - public static readonly METRICS_WS = 122; - public static readonly CLOSING_METRICS_LINE_COMMENT = 123; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 124; - public static readonly CLOSING_METRICS_WS = 125; + public static readonly MV_EXPAND = 10; + public static readonly RENAME = 11; + public static readonly ROW = 12; + public static readonly SHOW = 13; + public static readonly SORT = 14; + public static readonly STATS = 15; + public static readonly WHERE = 16; + public static readonly DEV_INLINESTATS = 17; + public static readonly DEV_LOOKUP = 18; + public static readonly DEV_MATCH = 19; + public static readonly DEV_METRICS = 20; + public static readonly UNKNOWN_CMD = 21; + public static readonly LINE_COMMENT = 22; + public static readonly MULTILINE_COMMENT = 23; + public static readonly WS = 24; + public static readonly PIPE = 25; + public static readonly QUOTED_STRING = 26; + public static readonly INTEGER_LITERAL = 27; + public static readonly DECIMAL_LITERAL = 28; + public static readonly BY = 29; + public static readonly AND = 30; + public static readonly ASC = 31; + public static readonly ASSIGN = 32; + public static readonly CAST_OP = 33; + public static readonly COMMA = 34; + public static readonly DESC = 35; + public static readonly DOT = 36; + public static readonly FALSE = 37; + public static readonly FIRST = 38; + public static readonly IN = 39; + public static readonly IS = 40; + public static readonly LAST = 41; + public static readonly LIKE = 42; + public static readonly LP = 43; + public static readonly NOT = 44; + public static readonly NULL = 45; + public static readonly NULLS = 46; + public static readonly OR = 47; + public static readonly PARAM = 48; + public static readonly RLIKE = 49; + public static readonly RP = 50; + public static readonly TRUE = 51; + public static readonly EQ = 52; + public static readonly CIEQ = 53; + public static readonly NEQ = 54; + public static readonly LT = 55; + public static readonly LTE = 56; + public static readonly GT = 57; + public static readonly GTE = 58; + public static readonly PLUS = 59; + public static readonly MINUS = 60; + public static readonly ASTERISK = 61; + public static readonly SLASH = 62; + public static readonly PERCENT = 63; + public static readonly NAMED_OR_POSITIONAL_PARAM = 64; + public static readonly OPENING_BRACKET = 65; + public static readonly CLOSING_BRACKET = 66; + public static readonly UNQUOTED_IDENTIFIER = 67; + public static readonly QUOTED_IDENTIFIER = 68; + public static readonly EXPR_LINE_COMMENT = 69; + public static readonly EXPR_MULTILINE_COMMENT = 70; + public static readonly EXPR_WS = 71; + public static readonly EXPLAIN_WS = 72; + public static readonly EXPLAIN_LINE_COMMENT = 73; + public static readonly EXPLAIN_MULTILINE_COMMENT = 74; + public static readonly METADATA = 75; + public static readonly UNQUOTED_SOURCE = 76; + public static readonly FROM_LINE_COMMENT = 77; + public static readonly FROM_MULTILINE_COMMENT = 78; + public static readonly FROM_WS = 79; + public static readonly ID_PATTERN = 80; + public static readonly PROJECT_LINE_COMMENT = 81; + public static readonly PROJECT_MULTILINE_COMMENT = 82; + public static readonly PROJECT_WS = 83; + public static readonly AS = 84; + public static readonly RENAME_LINE_COMMENT = 85; + public static readonly RENAME_MULTILINE_COMMENT = 86; + public static readonly RENAME_WS = 87; + public static readonly ON = 88; + public static readonly WITH = 89; + public static readonly ENRICH_POLICY_NAME = 90; + public static readonly ENRICH_LINE_COMMENT = 91; + public static readonly ENRICH_MULTILINE_COMMENT = 92; + public static readonly ENRICH_WS = 93; + public static readonly ENRICH_FIELD_LINE_COMMENT = 94; + public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 95; + public static readonly ENRICH_FIELD_WS = 96; + public static readonly MVEXPAND_LINE_COMMENT = 97; + public static readonly MVEXPAND_MULTILINE_COMMENT = 98; + public static readonly MVEXPAND_WS = 99; + public static readonly INFO = 100; + public static readonly SHOW_LINE_COMMENT = 101; + public static readonly SHOW_MULTILINE_COMMENT = 102; + public static readonly SHOW_WS = 103; + public static readonly COLON = 104; + public static readonly SETTING = 105; + public static readonly SETTING_LINE_COMMENT = 106; + public static readonly SETTTING_MULTILINE_COMMENT = 107; + public static readonly SETTING_WS = 108; + public static readonly LOOKUP_LINE_COMMENT = 109; + public static readonly LOOKUP_MULTILINE_COMMENT = 110; + public static readonly LOOKUP_WS = 111; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 112; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 113; + public static readonly LOOKUP_FIELD_WS = 114; + public static readonly METRICS_LINE_COMMENT = 115; + public static readonly METRICS_MULTILINE_COMMENT = 116; + public static readonly METRICS_WS = 117; + public static readonly CLOSING_METRICS_LINE_COMMENT = 118; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 119; + public static readonly CLOSING_METRICS_WS = 120; public static readonly EOF = Token.EOF; public static readonly EXPRESSION_MODE = 1; public static readonly EXPLAIN_MODE = 2; @@ -158,12 +153,11 @@ export default class esql_lexer extends lexer_config { public static readonly ENRICH_FIELD_MODE = 7; public static readonly MVEXPAND_MODE = 8; public static readonly SHOW_MODE = 9; - public static readonly META_MODE = 10; - public static readonly SETTING_MODE = 11; - public static readonly LOOKUP_MODE = 12; - public static readonly LOOKUP_FIELD_MODE = 13; - public static readonly METRICS_MODE = 14; - public static readonly CLOSING_METRICS_MODE = 15; + public static readonly SETTING_MODE = 10; + public static readonly LOOKUP_MODE = 11; + public static readonly LOOKUP_FIELD_MODE = 12; + public static readonly METRICS_MODE = 13; + public static readonly CLOSING_METRICS_MODE = 14; public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]; public static readonly literalNames: (string | null)[] = [ null, "'dissect'", @@ -171,7 +165,7 @@ export default class esql_lexer extends lexer_config { "'eval'", "'explain'", "'from'", "'grok'", "'keep'", "'limit'", - "'meta'", "'mv_expand'", + "'mv_expand'", "'rename'", "'row'", "'show'", "'sort'", "'stats'", @@ -219,15 +213,13 @@ export default class esql_lexer extends lexer_config { null, null, "'info'", null, null, null, - "'functions'", - null, null, - null, "':'" ]; + "':'" ]; public static readonly symbolicNames: (string | null)[] = [ null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", "KEEP", "LIMIT", - "META", "MV_EXPAND", + "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", "WHERE", @@ -297,10 +289,6 @@ export default class esql_lexer extends lexer_config { "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS", - "FUNCTIONS", - "META_LINE_COMMENT", - "META_MULTILINE_COMMENT", - "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", @@ -322,17 +310,17 @@ export default class esql_lexer extends lexer_config { "PROJECT_MODE", "RENAME_MODE", "ENRICH_MODE", "ENRICH_FIELD_MODE", "MVEXPAND_MODE", "SHOW_MODE", - "META_MODE", "SETTING_MODE", - "LOOKUP_MODE", "LOOKUP_FIELD_MODE", - "METRICS_MODE", "CLOSING_METRICS_MODE", ]; + "SETTING_MODE", "LOOKUP_MODE", + "LOOKUP_FIELD_MODE", "METRICS_MODE", + "CLOSING_METRICS_MODE", ]; public static readonly ruleNames: string[] = [ "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", "KEEP", - "LIMIT", "META", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", - "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", - "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", - "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", - "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", + "LIMIT", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", "WHERE", + "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", "UNKNOWN_CMD", + "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", "LETTER", + "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", "BACKQUOTE", + "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "LP", "NOT", "NULL", "NULLS", "OR", "PARAM", "RLIKE", "RP", "TRUE", "EQ", @@ -344,24 +332,27 @@ export default class esql_lexer extends lexer_config { "FROM_PIPE", "FROM_OPENING_BRACKET", "FROM_CLOSING_BRACKET", "FROM_COLON", "FROM_COMMA", "FROM_ASSIGN", "METADATA", "UNQUOTED_SOURCE_PART", "UNQUOTED_SOURCE", "FROM_UNQUOTED_SOURCE", "FROM_QUOTED_SOURCE", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", - "FROM_WS", "PROJECT_PIPE", "PROJECT_DOT", "PROJECT_COMMA", "UNQUOTED_ID_BODY_WITH_PATTERN", + "FROM_WS", "PROJECT_PIPE", "PROJECT_DOT", "PROJECT_COMMA", "PROJECT_PARAM", + "PROJECT_NAMED_OR_POSITIONAL_PARAM", "UNQUOTED_ID_BODY_WITH_PATTERN", "UNQUOTED_ID_PATTERN", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "RENAME_PIPE", "RENAME_ASSIGN", "RENAME_COMMA", "RENAME_DOT", - "AS", "RENAME_ID_PATTERN", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", - "RENAME_WS", "ENRICH_PIPE", "ENRICH_OPENING_BRACKET", "ON", "WITH", "ENRICH_POLICY_NAME_BODY", - "ENRICH_POLICY_NAME", "ENRICH_MODE_UNQUOTED_VALUE", "ENRICH_LINE_COMMENT", - "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_PIPE", "ENRICH_FIELD_ASSIGN", - "ENRICH_FIELD_COMMA", "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", "ENRICH_FIELD_ID_PATTERN", - "ENRICH_FIELD_QUOTED_IDENTIFIER", "ENRICH_FIELD_LINE_COMMENT", "ENRICH_FIELD_MULTILINE_COMMENT", - "ENRICH_FIELD_WS", "MVEXPAND_PIPE", "MVEXPAND_DOT", "MVEXPAND_QUOTED_IDENTIFIER", - "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", - "MVEXPAND_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", - "SHOW_WS", "META_PIPE", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", - "META_WS", "SETTING_CLOSING_BRACKET", "COLON", "SETTING", "SETTING_LINE_COMMENT", - "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "LOOKUP_PIPE", "LOOKUP_COLON", - "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", "LOOKUP_UNQUOTED_SOURCE", "LOOKUP_QUOTED_SOURCE", - "LOOKUP_LINE_COMMENT", "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_PIPE", - "LOOKUP_FIELD_COMMA", "LOOKUP_FIELD_DOT", "LOOKUP_FIELD_ID_PATTERN", "LOOKUP_FIELD_LINE_COMMENT", + "RENAME_PARAM", "RENAME_NAMED_OR_POSITIONAL_PARAM", "AS", "RENAME_ID_PATTERN", + "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", "ENRICH_PIPE", + "ENRICH_OPENING_BRACKET", "ON", "WITH", "ENRICH_POLICY_NAME_BODY", "ENRICH_POLICY_NAME", + "ENRICH_MODE_UNQUOTED_VALUE", "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", + "ENRICH_WS", "ENRICH_FIELD_PIPE", "ENRICH_FIELD_ASSIGN", "ENRICH_FIELD_COMMA", + "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", "ENRICH_FIELD_ID_PATTERN", "ENRICH_FIELD_QUOTED_IDENTIFIER", + "ENRICH_FIELD_PARAM", "ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM", "ENRICH_FIELD_LINE_COMMENT", + "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "MVEXPAND_PIPE", + "MVEXPAND_DOT", "MVEXPAND_PARAM", "MVEXPAND_NAMED_OR_POSITIONAL_PARAM", + "MVEXPAND_QUOTED_IDENTIFIER", "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", + "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", + "SHOW_MULTILINE_COMMENT", "SHOW_WS", "SETTING_CLOSING_BRACKET", "COLON", + "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", + "LOOKUP_PIPE", "LOOKUP_COLON", "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", + "LOOKUP_UNQUOTED_SOURCE", "LOOKUP_QUOTED_SOURCE", "LOOKUP_LINE_COMMENT", + "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_PIPE", "LOOKUP_FIELD_COMMA", + "LOOKUP_FIELD_DOT", "LOOKUP_FIELD_ID_PATTERN", "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "METRICS_PIPE", "METRICS_UNQUOTED_SOURCE", "METRICS_QUOTED_SOURCE", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", "CLOSING_METRICS_COLON", "CLOSING_METRICS_COMMA", "CLOSING_METRICS_LINE_COMMENT", @@ -390,15 +381,15 @@ export default class esql_lexer extends lexer_config { // @Override public sempred(localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { switch (ruleIndex) { - case 17: + case 16: return this.DEV_INLINESTATS_sempred(localctx, predIndex); - case 18: + case 17: return this.DEV_LOOKUP_sempred(localctx, predIndex); - case 19: + case 18: return this.DEV_MATCH_sempred(localctx, predIndex); - case 20: + case 19: return this.DEV_METRICS_sempred(localctx, predIndex); - case 74: + case 73: return this.DEV_MATCH_OP_sempred(localctx, predIndex); } return true; @@ -439,22 +430,22 @@ export default class esql_lexer extends lexer_config { return true; } - public static readonly _serializedATN: number[] = [4,0,125,1474,6,-1,6, - -1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1, - 2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8, - 2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16, - 7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7, - 23,2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30, - 2,31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2, - 38,7,38,2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45, - 7,45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7, - 52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,59,7,59, - 2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,65,2,66,7,66,2, - 67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2,72,7,72,2,73,7,73,2,74, - 7,74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7,78,2,79,7,79,2,80,7,80,2,81,7, - 81,2,82,7,82,2,83,7,83,2,84,7,84,2,85,7,85,2,86,7,86,2,87,7,87,2,88,7,88, - 2,89,7,89,2,90,7,90,2,91,7,91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2, - 96,7,96,2,97,7,97,2,98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102, + public static readonly _serializedATN: number[] = [4,0,120,1475,6,-1,6, + -1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,2,0, + 7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9, + 7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7, + 16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23, + 2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2, + 31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38, + 7,38,2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7, + 45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,52, + 2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,59,7,59,2, + 60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,65,2,66,7,66,2,67, + 7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2,72,7,72,2,73,7,73,2,74,7, + 74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7,78,2,79,7,79,2,80,7,80,2,81,7,81, + 2,82,7,82,2,83,7,83,2,84,7,84,2,85,7,85,2,86,7,86,2,87,7,87,2,88,7,88,2, + 89,7,89,2,90,7,90,2,91,7,91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2,96, + 7,96,2,97,7,97,2,98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102, 2,103,7,103,2,104,7,104,2,105,7,105,2,106,7,106,2,107,7,107,2,108,7,108, 2,109,7,109,2,110,7,110,2,111,7,111,2,112,7,112,2,113,7,113,2,114,7,114, 2,115,7,115,2,116,7,116,2,117,7,117,2,118,7,118,2,119,7,119,2,120,7,120, @@ -470,110 +461,110 @@ export default class esql_lexer extends lexer_config { 2,175,7,175,2,176,7,176,2,177,7,177,2,178,7,178,2,179,7,179,2,180,7,180, 2,181,7,181,2,182,7,182,2,183,7,183,2,184,7,184,2,185,7,185,2,186,7,186, 2,187,7,187,2,188,7,188,2,189,7,189,2,190,7,190,2,191,7,191,2,192,7,192, - 2,193,7,193,2,194,7,194,2,195,7,195,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0, - 1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3, - 1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5, - 1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7, - 1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1, - 10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11, - 1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1,13,1,13,1,13,1, - 13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15, - 1,15,1,15,1,15,1,15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1, - 17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18, - 1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,19,1,19,1, - 19,1,19,1,19,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,21, - 4,21,591,8,21,11,21,12,21,592,1,21,1,21,1,22,1,22,1,22,1,22,5,22,601,8, - 22,10,22,12,22,604,9,22,1,22,3,22,607,8,22,1,22,3,22,610,8,22,1,22,1,22, - 1,23,1,23,1,23,1,23,1,23,5,23,619,8,23,10,23,12,23,622,9,23,1,23,1,23,1, - 23,1,23,1,23,1,24,4,24,630,8,24,11,24,12,24,631,1,24,1,24,1,25,1,25,1,25, - 1,25,1,26,1,26,1,27,1,27,1,28,1,28,1,28,1,29,1,29,1,30,1,30,3,30,651,8, - 30,1,30,4,30,654,8,30,11,30,12,30,655,1,31,1,31,1,32,1,32,1,33,1,33,1,33, - 3,33,665,8,33,1,34,1,34,1,35,1,35,1,35,3,35,672,8,35,1,36,1,36,1,36,5,36, - 677,8,36,10,36,12,36,680,9,36,1,36,1,36,1,36,1,36,1,36,1,36,5,36,688,8, - 36,10,36,12,36,691,9,36,1,36,1,36,1,36,1,36,1,36,3,36,698,8,36,1,36,3,36, - 701,8,36,3,36,703,8,36,1,37,4,37,706,8,37,11,37,12,37,707,1,38,4,38,711, - 8,38,11,38,12,38,712,1,38,1,38,5,38,717,8,38,10,38,12,38,720,9,38,1,38, - 1,38,4,38,724,8,38,11,38,12,38,725,1,38,4,38,729,8,38,11,38,12,38,730,1, - 38,1,38,5,38,735,8,38,10,38,12,38,738,9,38,3,38,740,8,38,1,38,1,38,1,38, - 1,38,4,38,746,8,38,11,38,12,38,747,1,38,1,38,3,38,752,8,38,1,39,1,39,1, - 39,1,40,1,40,1,40,1,40,1,41,1,41,1,41,1,41,1,42,1,42,1,43,1,43,1,43,1,44, - 1,44,1,45,1,45,1,45,1,45,1,45,1,46,1,46,1,47,1,47,1,47,1,47,1,47,1,47,1, - 48,1,48,1,48,1,48,1,48,1,48,1,49,1,49,1,49,1,50,1,50,1,50,1,51,1,51,1,51, - 1,51,1,51,1,52,1,52,1,52,1,52,1,52,1,53,1,53,1,54,1,54,1,54,1,54,1,55,1, - 55,1,55,1,55,1,55,1,56,1,56,1,56,1,56,1,56,1,56,1,57,1,57,1,57,1,58,1,58, - 1,59,1,59,1,59,1,59,1,59,1,59,1,60,1,60,1,61,1,61,1,61,1,61,1,61,1,62,1, - 62,1,62,1,63,1,63,1,63,1,64,1,64,1,64,1,65,1,65,1,66,1,66,1,66,1,67,1,67, - 1,68,1,68,1,68,1,69,1,69,1,70,1,70,1,71,1,71,1,72,1,72,1,73,1,73,1,74,1, - 74,1,74,1,74,1,74,1,75,1,75,1,75,3,75,879,8,75,1,75,5,75,882,8,75,10,75, - 12,75,885,9,75,1,75,1,75,4,75,889,8,75,11,75,12,75,890,3,75,893,8,75,1, - 76,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,77,1,78,1,78,5,78,907,8,78, - 10,78,12,78,910,9,78,1,78,1,78,3,78,914,8,78,1,78,4,78,917,8,78,11,78,12, - 78,918,3,78,921,8,78,1,79,1,79,4,79,925,8,79,11,79,12,79,926,1,79,1,79, - 1,80,1,80,1,81,1,81,1,81,1,81,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1, - 84,1,84,1,84,1,84,1,84,1,85,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1,87, - 1,87,1,87,1,87,1,88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,89,1,90,1,90,1, - 90,1,90,1,91,1,91,1,91,1,91,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,94, - 1,94,1,94,1,94,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,96,1,96,1, - 96,3,96,1004,8,96,1,97,4,97,1007,8,97,11,97,12,97,1008,1,98,1,98,1,98,1, - 98,1,99,1,99,1,99,1,99,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101, - 1,102,1,102,1,102,1,102,1,103,1,103,1,103,1,103,1,103,1,104,1,104,1,104, - 1,104,1,105,1,105,1,105,1,105,1,106,1,106,1,106,1,106,3,106,1048,8,106, - 1,107,1,107,3,107,1052,8,107,1,107,5,107,1055,8,107,10,107,12,107,1058, - 9,107,1,107,1,107,3,107,1062,8,107,1,107,4,107,1065,8,107,11,107,12,107, - 1066,3,107,1069,8,107,1,108,1,108,4,108,1073,8,108,11,108,12,108,1074,1, - 109,1,109,1,109,1,109,1,110,1,110,1,110,1,110,1,111,1,111,1,111,1,111,1, - 112,1,112,1,112,1,112,1,112,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1, - 114,1,115,1,115,1,115,1,115,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1, - 118,1,118,1,118,1,118,1,119,1,119,1,119,1,119,1,120,1,120,1,120,1,120,1, - 121,1,121,1,121,1,121,1,121,1,122,1,122,1,122,1,122,1,122,1,123,1,123,1, - 123,1,123,1,123,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,125,1,125,1, - 126,4,126,1150,8,126,11,126,12,126,1151,1,126,1,126,3,126,1156,8,126,1, - 126,4,126,1159,8,126,11,126,12,126,1160,1,127,1,127,1,127,1,127,1,128,1, - 128,1,128,1,128,1,129,1,129,1,129,1,129,1,130,1,130,1,130,1,130,1,131,1, - 131,1,131,1,131,1,131,1,131,1,132,1,132,1,132,1,132,1,133,1,133,1,133,1, - 133,1,134,1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,136,1,136,1,136,1, - 136,1,137,1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1,139,1, - 139,1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,141,1,142,1,142,1, - 142,1,142,1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1,145,1,145,1, - 145,1,145,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1,148,1,148,1, - 148,1,148,1,148,1,149,1,149,1,149,1,149,1,149,1,150,1,150,1,150,1,150,1, - 151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,153,1,153,1,153,1,153,1, - 153,1,154,1,154,1,154,1,154,1,154,1,154,1,154,1,154,1,154,1,154,1,155,1, - 155,1,155,1,155,1,156,1,156,1,156,1,156,1,157,1,157,1,157,1,157,1,158,1, - 158,1,158,1,158,1,158,1,159,1,159,1,160,1,160,1,160,1,160,1,160,4,160,1311, - 8,160,11,160,12,160,1312,1,161,1,161,1,161,1,161,1,162,1,162,1,162,1,162, - 1,163,1,163,1,163,1,163,1,164,1,164,1,164,1,164,1,164,1,165,1,165,1,165, - 1,165,1,166,1,166,1,166,1,166,1,167,1,167,1,167,1,167,1,168,1,168,1,168, - 1,168,1,168,1,169,1,169,1,169,1,169,1,170,1,170,1,170,1,170,1,171,1,171, - 1,171,1,171,1,172,1,172,1,172,1,172,1,173,1,173,1,173,1,173,1,174,1,174, - 1,174,1,174,1,174,1,174,1,175,1,175,1,175,1,175,1,176,1,176,1,176,1,176, - 1,177,1,177,1,177,1,177,1,178,1,178,1,178,1,178,1,179,1,179,1,179,1,179, - 1,180,1,180,1,180,1,180,1,181,1,181,1,181,1,181,1,181,1,182,1,182,1,182, - 1,182,1,182,1,182,1,183,1,183,1,183,1,183,1,183,1,183,1,184,1,184,1,184, - 1,184,1,185,1,185,1,185,1,185,1,186,1,186,1,186,1,186,1,187,1,187,1,187, - 1,187,1,187,1,187,1,188,1,188,1,188,1,188,1,188,1,188,1,189,1,189,1,189, - 1,189,1,190,1,190,1,190,1,190,1,191,1,191,1,191,1,191,1,192,1,192,1,192, - 1,192,1,192,1,192,1,193,1,193,1,193,1,193,1,193,1,193,1,194,1,194,1,194, - 1,194,1,194,1,194,1,195,1,195,1,195,1,195,1,195,2,620,689,0,196,16,1,18, - 2,20,3,22,4,24,5,26,6,28,7,30,8,32,9,34,10,36,11,38,12,40,13,42,14,44,15, - 46,16,48,17,50,18,52,19,54,20,56,21,58,22,60,23,62,24,64,25,66,26,68,0, - 70,0,72,0,74,0,76,0,78,0,80,0,82,0,84,0,86,0,88,27,90,28,92,29,94,30,96, - 31,98,32,100,33,102,34,104,35,106,36,108,37,110,38,112,39,114,40,116,41, - 118,42,120,43,122,44,124,45,126,46,128,47,130,48,132,49,134,50,136,51,138, - 52,140,53,142,54,144,55,146,56,148,57,150,58,152,59,154,60,156,61,158,62, - 160,63,162,64,164,0,166,65,168,66,170,67,172,68,174,0,176,69,178,70,180, - 71,182,72,184,0,186,0,188,73,190,74,192,75,194,0,196,0,198,0,200,0,202, - 0,204,0,206,76,208,0,210,77,212,0,214,0,216,78,218,79,220,80,222,0,224, - 0,226,0,228,0,230,0,232,81,234,82,236,83,238,84,240,0,242,0,244,0,246,0, - 248,85,250,0,252,86,254,87,256,88,258,0,260,0,262,89,264,90,266,0,268,91, - 270,0,272,92,274,93,276,94,278,0,280,0,282,0,284,0,286,0,288,0,290,0,292, - 95,294,96,296,97,298,0,300,0,302,0,304,0,306,98,308,99,310,100,312,0,314, - 101,316,102,318,103,320,104,322,0,324,105,326,106,328,107,330,108,332,0, - 334,109,336,110,338,111,340,112,342,113,344,0,346,0,348,0,350,0,352,0,354, - 0,356,0,358,114,360,115,362,116,364,0,366,0,368,0,370,0,372,117,374,118, - 376,119,378,0,380,0,382,0,384,120,386,121,388,122,390,0,392,0,394,123,396, - 124,398,125,400,0,402,0,404,0,406,0,16,0,1,2,3,4,5,6,7,8,9,10,11,12,13, - 14,15,35,2,0,68,68,100,100,2,0,73,73,105,105,2,0,83,83,115,115,2,0,69,69, + 2,193,7,193,2,194,7,194,2,195,7,195,2,196,7,196,2,197,7,197,1,0,1,0,1,0, + 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2, + 1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4, + 1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9, + 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10, + 1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1, + 12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14, + 1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1,16,1,16,1, + 16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17, + 1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1, + 19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,20,4,20,587,8,20, + 11,20,12,20,588,1,20,1,20,1,21,1,21,1,21,1,21,5,21,597,8,21,10,21,12,21, + 600,9,21,1,21,3,21,603,8,21,1,21,3,21,606,8,21,1,21,1,21,1,22,1,22,1,22, + 1,22,1,22,5,22,615,8,22,10,22,12,22,618,9,22,1,22,1,22,1,22,1,22,1,22,1, + 23,4,23,626,8,23,11,23,12,23,627,1,23,1,23,1,24,1,24,1,24,1,24,1,25,1,25, + 1,26,1,26,1,27,1,27,1,27,1,28,1,28,1,29,1,29,3,29,647,8,29,1,29,4,29,650, + 8,29,11,29,12,29,651,1,30,1,30,1,31,1,31,1,32,1,32,1,32,3,32,661,8,32,1, + 33,1,33,1,34,1,34,1,34,3,34,668,8,34,1,35,1,35,1,35,5,35,673,8,35,10,35, + 12,35,676,9,35,1,35,1,35,1,35,1,35,1,35,1,35,5,35,684,8,35,10,35,12,35, + 687,9,35,1,35,1,35,1,35,1,35,1,35,3,35,694,8,35,1,35,3,35,697,8,35,3,35, + 699,8,35,1,36,4,36,702,8,36,11,36,12,36,703,1,37,4,37,707,8,37,11,37,12, + 37,708,1,37,1,37,5,37,713,8,37,10,37,12,37,716,9,37,1,37,1,37,4,37,720, + 8,37,11,37,12,37,721,1,37,4,37,725,8,37,11,37,12,37,726,1,37,1,37,5,37, + 731,8,37,10,37,12,37,734,9,37,3,37,736,8,37,1,37,1,37,1,37,1,37,4,37,742, + 8,37,11,37,12,37,743,1,37,1,37,3,37,748,8,37,1,38,1,38,1,38,1,39,1,39,1, + 39,1,39,1,40,1,40,1,40,1,40,1,41,1,41,1,42,1,42,1,42,1,43,1,43,1,44,1,44, + 1,44,1,44,1,44,1,45,1,45,1,46,1,46,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1, + 47,1,47,1,47,1,48,1,48,1,48,1,49,1,49,1,49,1,50,1,50,1,50,1,50,1,50,1,51, + 1,51,1,51,1,51,1,51,1,52,1,52,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1, + 54,1,55,1,55,1,55,1,55,1,55,1,55,1,56,1,56,1,56,1,57,1,57,1,58,1,58,1,58, + 1,58,1,58,1,58,1,59,1,59,1,60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1, + 62,1,62,1,63,1,63,1,63,1,64,1,64,1,65,1,65,1,65,1,66,1,66,1,67,1,67,1,67, + 1,68,1,68,1,69,1,69,1,70,1,70,1,71,1,71,1,72,1,72,1,73,1,73,1,73,1,73,1, + 73,1,74,1,74,1,74,3,74,875,8,74,1,74,5,74,878,8,74,10,74,12,74,881,9,74, + 1,74,1,74,4,74,885,8,74,11,74,12,74,886,3,74,889,8,74,1,75,1,75,1,75,1, + 75,1,75,1,76,1,76,1,76,1,76,1,76,1,77,1,77,5,77,903,8,77,10,77,12,77,906, + 9,77,1,77,1,77,3,77,910,8,77,1,77,4,77,913,8,77,11,77,12,77,914,3,77,917, + 8,77,1,78,1,78,4,78,921,8,78,11,78,12,78,922,1,78,1,78,1,79,1,79,1,80,1, + 80,1,80,1,80,1,81,1,81,1,81,1,81,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83, + 1,83,1,84,1,84,1,84,1,84,1,84,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1, + 87,1,87,1,87,1,87,1,88,1,88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,90,1,90, + 1,90,1,90,1,91,1,91,1,91,1,91,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1, + 94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,95,1,95,1,95,3,95,1000,8,95, + 1,96,4,96,1003,8,96,11,96,12,96,1004,1,97,1,97,1,97,1,97,1,98,1,98,1,98, + 1,98,1,99,1,99,1,99,1,99,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101, + 1,102,1,102,1,102,1,102,1,102,1,103,1,103,1,103,1,103,1,104,1,104,1,104, + 1,104,1,105,1,105,1,105,1,105,1,106,1,106,1,106,1,106,1,107,1,107,1,107, + 1,107,3,107,1052,8,107,1,108,1,108,3,108,1056,8,108,1,108,5,108,1059,8, + 108,10,108,12,108,1062,9,108,1,108,1,108,3,108,1066,8,108,1,108,4,108,1069, + 8,108,11,108,12,108,1070,3,108,1073,8,108,1,109,1,109,4,109,1077,8,109, + 11,109,12,109,1078,1,110,1,110,1,110,1,110,1,111,1,111,1,111,1,111,1,112, + 1,112,1,112,1,112,1,113,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1,114, + 1,115,1,115,1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117, + 1,118,1,118,1,118,1,118,1,119,1,119,1,119,1,120,1,120,1,120,1,120,1,121, + 1,121,1,121,1,121,1,122,1,122,1,122,1,122,1,123,1,123,1,123,1,123,1,124, + 1,124,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,125,1,126,1,126,1,126, + 1,126,1,126,1,127,1,127,1,127,1,127,1,127,1,127,1,127,1,128,1,128,1,129, + 4,129,1162,8,129,11,129,12,129,1163,1,129,1,129,3,129,1168,8,129,1,129, + 4,129,1171,8,129,11,129,12,129,1172,1,130,1,130,1,130,1,130,1,131,1,131, + 1,131,1,131,1,132,1,132,1,132,1,132,1,133,1,133,1,133,1,133,1,134,1,134, + 1,134,1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,136,1,136,1,136,1,136, + 1,137,1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1,139,1,139, + 1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,142,1,142,1,142,1,142, + 1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1,145,1,145,1,145,1,145, + 1,146,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1,148,1,148,1,148, + 1,148,1,149,1,149,1,149,1,149,1,150,1,150,1,150,1,150,1,151,1,151,1,151, + 1,151,1,152,1,152,1,152,1,152,1,153,1,153,1,153,1,153,1,154,1,154,1,154, + 1,154,1,155,1,155,1,155,1,155,1,155,1,156,1,156,1,156,1,156,1,156,1,157, + 1,157,1,157,1,157,1,158,1,158,1,158,1,158,1,159,1,159,1,159,1,159,1,160, + 1,160,1,160,1,160,1,160,1,161,1,161,1,162,1,162,1,162,1,162,1,162,4,162, + 1312,8,162,11,162,12,162,1313,1,163,1,163,1,163,1,163,1,164,1,164,1,164, + 1,164,1,165,1,165,1,165,1,165,1,166,1,166,1,166,1,166,1,166,1,167,1,167, + 1,167,1,167,1,168,1,168,1,168,1,168,1,169,1,169,1,169,1,169,1,170,1,170, + 1,170,1,170,1,170,1,171,1,171,1,171,1,171,1,172,1,172,1,172,1,172,1,173, + 1,173,1,173,1,173,1,174,1,174,1,174,1,174,1,175,1,175,1,175,1,175,1,176, + 1,176,1,176,1,176,1,176,1,176,1,177,1,177,1,177,1,177,1,178,1,178,1,178, + 1,178,1,179,1,179,1,179,1,179,1,180,1,180,1,180,1,180,1,181,1,181,1,181, + 1,181,1,182,1,182,1,182,1,182,1,183,1,183,1,183,1,183,1,183,1,184,1,184, + 1,184,1,184,1,184,1,184,1,185,1,185,1,185,1,185,1,185,1,185,1,186,1,186, + 1,186,1,186,1,187,1,187,1,187,1,187,1,188,1,188,1,188,1,188,1,189,1,189, + 1,189,1,189,1,189,1,189,1,190,1,190,1,190,1,190,1,190,1,190,1,191,1,191, + 1,191,1,191,1,192,1,192,1,192,1,192,1,193,1,193,1,193,1,193,1,194,1,194, + 1,194,1,194,1,194,1,194,1,195,1,195,1,195,1,195,1,195,1,195,1,196,1,196, + 1,196,1,196,1,196,1,196,1,197,1,197,1,197,1,197,1,197,2,616,685,0,198,15, + 1,17,2,19,3,21,4,23,5,25,6,27,7,29,8,31,9,33,10,35,11,37,12,39,13,41,14, + 43,15,45,16,47,17,49,18,51,19,53,20,55,21,57,22,59,23,61,24,63,25,65,0, + 67,0,69,0,71,0,73,0,75,0,77,0,79,0,81,0,83,0,85,26,87,27,89,28,91,29,93, + 30,95,31,97,32,99,33,101,34,103,35,105,36,107,37,109,38,111,39,113,40,115, + 41,117,42,119,43,121,44,123,45,125,46,127,47,129,48,131,49,133,50,135,51, + 137,52,139,53,141,54,143,55,145,56,147,57,149,58,151,59,153,60,155,61,157, + 62,159,63,161,0,163,64,165,65,167,66,169,67,171,0,173,68,175,69,177,70, + 179,71,181,0,183,0,185,72,187,73,189,74,191,0,193,0,195,0,197,0,199,0,201, + 0,203,75,205,0,207,76,209,0,211,0,213,77,215,78,217,79,219,0,221,0,223, + 0,225,0,227,0,229,0,231,0,233,80,235,81,237,82,239,83,241,0,243,0,245,0, + 247,0,249,0,251,0,253,84,255,0,257,85,259,86,261,87,263,0,265,0,267,88, + 269,89,271,0,273,90,275,0,277,91,279,92,281,93,283,0,285,0,287,0,289,0, + 291,0,293,0,295,0,297,0,299,0,301,94,303,95,305,96,307,0,309,0,311,0,313, + 0,315,0,317,0,319,97,321,98,323,99,325,0,327,100,329,101,331,102,333,103, + 335,0,337,104,339,105,341,106,343,107,345,108,347,0,349,0,351,0,353,0,355, + 0,357,0,359,0,361,109,363,110,365,111,367,0,369,0,371,0,373,0,375,112,377, + 113,379,114,381,0,383,0,385,0,387,115,389,116,391,117,393,0,395,0,397,118, + 399,119,401,120,403,0,405,0,407,0,409,0,15,0,1,2,3,4,5,6,7,8,9,10,11,12, + 13,14,35,2,0,68,68,100,100,2,0,73,73,105,105,2,0,83,83,115,115,2,0,69,69, 101,101,2,0,67,67,99,99,2,0,84,84,116,116,2,0,82,82,114,114,2,0,79,79,111, 111,2,0,80,80,112,112,2,0,78,78,110,110,2,0,72,72,104,104,2,0,86,86,118, 118,2,0,65,65,97,97,2,0,76,76,108,108,2,0,88,88,120,120,2,0,70,70,102,102, @@ -583,368 +574,369 @@ export default class esql_lexer extends lexer_config { 84,92,92,110,110,114,114,116,116,4,0,10,10,13,13,34,34,92,92,2,0,43,43, 45,45,1,0,96,96,2,0,66,66,98,98,2,0,89,89,121,121,11,0,9,10,13,13,32,32, 34,34,44,44,47,47,58,58,61,61,91,91,93,93,124,124,2,0,42,42,47,47,11,0, - 9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62,63,92,92,124,124,1501, - 0,16,1,0,0,0,0,18,1,0,0,0,0,20,1,0,0,0,0,22,1,0,0,0,0,24,1,0,0,0,0,26,1, - 0,0,0,0,28,1,0,0,0,0,30,1,0,0,0,0,32,1,0,0,0,0,34,1,0,0,0,0,36,1,0,0,0, - 0,38,1,0,0,0,0,40,1,0,0,0,0,42,1,0,0,0,0,44,1,0,0,0,0,46,1,0,0,0,0,48,1, - 0,0,0,0,50,1,0,0,0,0,52,1,0,0,0,0,54,1,0,0,0,0,56,1,0,0,0,0,58,1,0,0,0, - 0,60,1,0,0,0,0,62,1,0,0,0,0,64,1,0,0,0,1,66,1,0,0,0,1,88,1,0,0,0,1,90,1, - 0,0,0,1,92,1,0,0,0,1,94,1,0,0,0,1,96,1,0,0,0,1,98,1,0,0,0,1,100,1,0,0,0, - 1,102,1,0,0,0,1,104,1,0,0,0,1,106,1,0,0,0,1,108,1,0,0,0,1,110,1,0,0,0,1, - 112,1,0,0,0,1,114,1,0,0,0,1,116,1,0,0,0,1,118,1,0,0,0,1,120,1,0,0,0,1,122, - 1,0,0,0,1,124,1,0,0,0,1,126,1,0,0,0,1,128,1,0,0,0,1,130,1,0,0,0,1,132,1, - 0,0,0,1,134,1,0,0,0,1,136,1,0,0,0,1,138,1,0,0,0,1,140,1,0,0,0,1,142,1,0, - 0,0,1,144,1,0,0,0,1,146,1,0,0,0,1,148,1,0,0,0,1,150,1,0,0,0,1,152,1,0,0, - 0,1,154,1,0,0,0,1,156,1,0,0,0,1,158,1,0,0,0,1,160,1,0,0,0,1,162,1,0,0,0, - 1,164,1,0,0,0,1,166,1,0,0,0,1,168,1,0,0,0,1,170,1,0,0,0,1,172,1,0,0,0,1, - 176,1,0,0,0,1,178,1,0,0,0,1,180,1,0,0,0,1,182,1,0,0,0,2,184,1,0,0,0,2,186, - 1,0,0,0,2,188,1,0,0,0,2,190,1,0,0,0,2,192,1,0,0,0,3,194,1,0,0,0,3,196,1, - 0,0,0,3,198,1,0,0,0,3,200,1,0,0,0,3,202,1,0,0,0,3,204,1,0,0,0,3,206,1,0, - 0,0,3,210,1,0,0,0,3,212,1,0,0,0,3,214,1,0,0,0,3,216,1,0,0,0,3,218,1,0,0, - 0,3,220,1,0,0,0,4,222,1,0,0,0,4,224,1,0,0,0,4,226,1,0,0,0,4,232,1,0,0,0, - 4,234,1,0,0,0,4,236,1,0,0,0,4,238,1,0,0,0,5,240,1,0,0,0,5,242,1,0,0,0,5, - 244,1,0,0,0,5,246,1,0,0,0,5,248,1,0,0,0,5,250,1,0,0,0,5,252,1,0,0,0,5,254, - 1,0,0,0,5,256,1,0,0,0,6,258,1,0,0,0,6,260,1,0,0,0,6,262,1,0,0,0,6,264,1, - 0,0,0,6,268,1,0,0,0,6,270,1,0,0,0,6,272,1,0,0,0,6,274,1,0,0,0,6,276,1,0, - 0,0,7,278,1,0,0,0,7,280,1,0,0,0,7,282,1,0,0,0,7,284,1,0,0,0,7,286,1,0,0, - 0,7,288,1,0,0,0,7,290,1,0,0,0,7,292,1,0,0,0,7,294,1,0,0,0,7,296,1,0,0,0, - 8,298,1,0,0,0,8,300,1,0,0,0,8,302,1,0,0,0,8,304,1,0,0,0,8,306,1,0,0,0,8, - 308,1,0,0,0,8,310,1,0,0,0,9,312,1,0,0,0,9,314,1,0,0,0,9,316,1,0,0,0,9,318, - 1,0,0,0,9,320,1,0,0,0,10,322,1,0,0,0,10,324,1,0,0,0,10,326,1,0,0,0,10,328, - 1,0,0,0,10,330,1,0,0,0,11,332,1,0,0,0,11,334,1,0,0,0,11,336,1,0,0,0,11, - 338,1,0,0,0,11,340,1,0,0,0,11,342,1,0,0,0,12,344,1,0,0,0,12,346,1,0,0,0, - 12,348,1,0,0,0,12,350,1,0,0,0,12,352,1,0,0,0,12,354,1,0,0,0,12,356,1,0, - 0,0,12,358,1,0,0,0,12,360,1,0,0,0,12,362,1,0,0,0,13,364,1,0,0,0,13,366, - 1,0,0,0,13,368,1,0,0,0,13,370,1,0,0,0,13,372,1,0,0,0,13,374,1,0,0,0,13, - 376,1,0,0,0,14,378,1,0,0,0,14,380,1,0,0,0,14,382,1,0,0,0,14,384,1,0,0,0, - 14,386,1,0,0,0,14,388,1,0,0,0,15,390,1,0,0,0,15,392,1,0,0,0,15,394,1,0, - 0,0,15,396,1,0,0,0,15,398,1,0,0,0,15,400,1,0,0,0,15,402,1,0,0,0,15,404, - 1,0,0,0,15,406,1,0,0,0,16,408,1,0,0,0,18,418,1,0,0,0,20,425,1,0,0,0,22, - 434,1,0,0,0,24,441,1,0,0,0,26,451,1,0,0,0,28,458,1,0,0,0,30,465,1,0,0,0, - 32,472,1,0,0,0,34,480,1,0,0,0,36,487,1,0,0,0,38,499,1,0,0,0,40,508,1,0, - 0,0,42,514,1,0,0,0,44,521,1,0,0,0,46,528,1,0,0,0,48,536,1,0,0,0,50,544, - 1,0,0,0,52,559,1,0,0,0,54,569,1,0,0,0,56,578,1,0,0,0,58,590,1,0,0,0,60, - 596,1,0,0,0,62,613,1,0,0,0,64,629,1,0,0,0,66,635,1,0,0,0,68,639,1,0,0,0, - 70,641,1,0,0,0,72,643,1,0,0,0,74,646,1,0,0,0,76,648,1,0,0,0,78,657,1,0, - 0,0,80,659,1,0,0,0,82,664,1,0,0,0,84,666,1,0,0,0,86,671,1,0,0,0,88,702, - 1,0,0,0,90,705,1,0,0,0,92,751,1,0,0,0,94,753,1,0,0,0,96,756,1,0,0,0,98, - 760,1,0,0,0,100,764,1,0,0,0,102,766,1,0,0,0,104,769,1,0,0,0,106,771,1,0, - 0,0,108,776,1,0,0,0,110,778,1,0,0,0,112,784,1,0,0,0,114,790,1,0,0,0,116, - 793,1,0,0,0,118,796,1,0,0,0,120,801,1,0,0,0,122,806,1,0,0,0,124,808,1,0, - 0,0,126,812,1,0,0,0,128,817,1,0,0,0,130,823,1,0,0,0,132,826,1,0,0,0,134, - 828,1,0,0,0,136,834,1,0,0,0,138,836,1,0,0,0,140,841,1,0,0,0,142,844,1,0, - 0,0,144,847,1,0,0,0,146,850,1,0,0,0,148,852,1,0,0,0,150,855,1,0,0,0,152, - 857,1,0,0,0,154,860,1,0,0,0,156,862,1,0,0,0,158,864,1,0,0,0,160,866,1,0, - 0,0,162,868,1,0,0,0,164,870,1,0,0,0,166,892,1,0,0,0,168,894,1,0,0,0,170, - 899,1,0,0,0,172,920,1,0,0,0,174,922,1,0,0,0,176,930,1,0,0,0,178,932,1,0, - 0,0,180,936,1,0,0,0,182,940,1,0,0,0,184,944,1,0,0,0,186,949,1,0,0,0,188, - 954,1,0,0,0,190,958,1,0,0,0,192,962,1,0,0,0,194,966,1,0,0,0,196,971,1,0, - 0,0,198,975,1,0,0,0,200,979,1,0,0,0,202,983,1,0,0,0,204,987,1,0,0,0,206, - 991,1,0,0,0,208,1003,1,0,0,0,210,1006,1,0,0,0,212,1010,1,0,0,0,214,1014, - 1,0,0,0,216,1018,1,0,0,0,218,1022,1,0,0,0,220,1026,1,0,0,0,222,1030,1,0, - 0,0,224,1035,1,0,0,0,226,1039,1,0,0,0,228,1047,1,0,0,0,230,1068,1,0,0,0, - 232,1072,1,0,0,0,234,1076,1,0,0,0,236,1080,1,0,0,0,238,1084,1,0,0,0,240, - 1088,1,0,0,0,242,1093,1,0,0,0,244,1097,1,0,0,0,246,1101,1,0,0,0,248,1105, - 1,0,0,0,250,1108,1,0,0,0,252,1112,1,0,0,0,254,1116,1,0,0,0,256,1120,1,0, - 0,0,258,1124,1,0,0,0,260,1129,1,0,0,0,262,1134,1,0,0,0,264,1139,1,0,0,0, - 266,1146,1,0,0,0,268,1155,1,0,0,0,270,1162,1,0,0,0,272,1166,1,0,0,0,274, - 1170,1,0,0,0,276,1174,1,0,0,0,278,1178,1,0,0,0,280,1184,1,0,0,0,282,1188, - 1,0,0,0,284,1192,1,0,0,0,286,1196,1,0,0,0,288,1200,1,0,0,0,290,1204,1,0, - 0,0,292,1208,1,0,0,0,294,1212,1,0,0,0,296,1216,1,0,0,0,298,1220,1,0,0,0, - 300,1225,1,0,0,0,302,1229,1,0,0,0,304,1233,1,0,0,0,306,1237,1,0,0,0,308, - 1241,1,0,0,0,310,1245,1,0,0,0,312,1249,1,0,0,0,314,1254,1,0,0,0,316,1259, - 1,0,0,0,318,1263,1,0,0,0,320,1267,1,0,0,0,322,1271,1,0,0,0,324,1276,1,0, - 0,0,326,1286,1,0,0,0,328,1290,1,0,0,0,330,1294,1,0,0,0,332,1298,1,0,0,0, - 334,1303,1,0,0,0,336,1310,1,0,0,0,338,1314,1,0,0,0,340,1318,1,0,0,0,342, - 1322,1,0,0,0,344,1326,1,0,0,0,346,1331,1,0,0,0,348,1335,1,0,0,0,350,1339, - 1,0,0,0,352,1343,1,0,0,0,354,1348,1,0,0,0,356,1352,1,0,0,0,358,1356,1,0, - 0,0,360,1360,1,0,0,0,362,1364,1,0,0,0,364,1368,1,0,0,0,366,1374,1,0,0,0, - 368,1378,1,0,0,0,370,1382,1,0,0,0,372,1386,1,0,0,0,374,1390,1,0,0,0,376, - 1394,1,0,0,0,378,1398,1,0,0,0,380,1403,1,0,0,0,382,1409,1,0,0,0,384,1415, - 1,0,0,0,386,1419,1,0,0,0,388,1423,1,0,0,0,390,1427,1,0,0,0,392,1433,1,0, - 0,0,394,1439,1,0,0,0,396,1443,1,0,0,0,398,1447,1,0,0,0,400,1451,1,0,0,0, - 402,1457,1,0,0,0,404,1463,1,0,0,0,406,1469,1,0,0,0,408,409,7,0,0,0,409, - 410,7,1,0,0,410,411,7,2,0,0,411,412,7,2,0,0,412,413,7,3,0,0,413,414,7,4, - 0,0,414,415,7,5,0,0,415,416,1,0,0,0,416,417,6,0,0,0,417,17,1,0,0,0,418, - 419,7,0,0,0,419,420,7,6,0,0,420,421,7,7,0,0,421,422,7,8,0,0,422,423,1,0, - 0,0,423,424,6,1,1,0,424,19,1,0,0,0,425,426,7,3,0,0,426,427,7,9,0,0,427, - 428,7,6,0,0,428,429,7,1,0,0,429,430,7,4,0,0,430,431,7,10,0,0,431,432,1, - 0,0,0,432,433,6,2,2,0,433,21,1,0,0,0,434,435,7,3,0,0,435,436,7,11,0,0,436, - 437,7,12,0,0,437,438,7,13,0,0,438,439,1,0,0,0,439,440,6,3,0,0,440,23,1, - 0,0,0,441,442,7,3,0,0,442,443,7,14,0,0,443,444,7,8,0,0,444,445,7,13,0,0, - 445,446,7,12,0,0,446,447,7,1,0,0,447,448,7,9,0,0,448,449,1,0,0,0,449,450, - 6,4,3,0,450,25,1,0,0,0,451,452,7,15,0,0,452,453,7,6,0,0,453,454,7,7,0,0, - 454,455,7,16,0,0,455,456,1,0,0,0,456,457,6,5,4,0,457,27,1,0,0,0,458,459, - 7,17,0,0,459,460,7,6,0,0,460,461,7,7,0,0,461,462,7,18,0,0,462,463,1,0,0, - 0,463,464,6,6,0,0,464,29,1,0,0,0,465,466,7,18,0,0,466,467,7,3,0,0,467,468, - 7,3,0,0,468,469,7,8,0,0,469,470,1,0,0,0,470,471,6,7,1,0,471,31,1,0,0,0, - 472,473,7,13,0,0,473,474,7,1,0,0,474,475,7,16,0,0,475,476,7,1,0,0,476,477, - 7,5,0,0,477,478,1,0,0,0,478,479,6,8,0,0,479,33,1,0,0,0,480,481,7,16,0,0, - 481,482,7,3,0,0,482,483,7,5,0,0,483,484,7,12,0,0,484,485,1,0,0,0,485,486, - 6,9,5,0,486,35,1,0,0,0,487,488,7,16,0,0,488,489,7,11,0,0,489,490,5,95,0, - 0,490,491,7,3,0,0,491,492,7,14,0,0,492,493,7,8,0,0,493,494,7,12,0,0,494, - 495,7,9,0,0,495,496,7,0,0,0,496,497,1,0,0,0,497,498,6,10,6,0,498,37,1,0, - 0,0,499,500,7,6,0,0,500,501,7,3,0,0,501,502,7,9,0,0,502,503,7,12,0,0,503, - 504,7,16,0,0,504,505,7,3,0,0,505,506,1,0,0,0,506,507,6,11,7,0,507,39,1, - 0,0,0,508,509,7,6,0,0,509,510,7,7,0,0,510,511,7,19,0,0,511,512,1,0,0,0, - 512,513,6,12,0,0,513,41,1,0,0,0,514,515,7,2,0,0,515,516,7,10,0,0,516,517, - 7,7,0,0,517,518,7,19,0,0,518,519,1,0,0,0,519,520,6,13,8,0,520,43,1,0,0, - 0,521,522,7,2,0,0,522,523,7,7,0,0,523,524,7,6,0,0,524,525,7,5,0,0,525,526, - 1,0,0,0,526,527,6,14,0,0,527,45,1,0,0,0,528,529,7,2,0,0,529,530,7,5,0,0, - 530,531,7,12,0,0,531,532,7,5,0,0,532,533,7,2,0,0,533,534,1,0,0,0,534,535, - 6,15,0,0,535,47,1,0,0,0,536,537,7,19,0,0,537,538,7,10,0,0,538,539,7,3,0, - 0,539,540,7,6,0,0,540,541,7,3,0,0,541,542,1,0,0,0,542,543,6,16,0,0,543, - 49,1,0,0,0,544,545,4,17,0,0,545,546,7,1,0,0,546,547,7,9,0,0,547,548,7,13, - 0,0,548,549,7,1,0,0,549,550,7,9,0,0,550,551,7,3,0,0,551,552,7,2,0,0,552, - 553,7,5,0,0,553,554,7,12,0,0,554,555,7,5,0,0,555,556,7,2,0,0,556,557,1, - 0,0,0,557,558,6,17,0,0,558,51,1,0,0,0,559,560,4,18,1,0,560,561,7,13,0,0, - 561,562,7,7,0,0,562,563,7,7,0,0,563,564,7,18,0,0,564,565,7,20,0,0,565,566, - 7,8,0,0,566,567,1,0,0,0,567,568,6,18,9,0,568,53,1,0,0,0,569,570,4,19,2, - 0,570,571,7,16,0,0,571,572,7,12,0,0,572,573,7,5,0,0,573,574,7,4,0,0,574, - 575,7,10,0,0,575,576,1,0,0,0,576,577,6,19,0,0,577,55,1,0,0,0,578,579,4, - 20,3,0,579,580,7,16,0,0,580,581,7,3,0,0,581,582,7,5,0,0,582,583,7,6,0,0, - 583,584,7,1,0,0,584,585,7,4,0,0,585,586,7,2,0,0,586,587,1,0,0,0,587,588, - 6,20,10,0,588,57,1,0,0,0,589,591,8,21,0,0,590,589,1,0,0,0,591,592,1,0,0, - 0,592,590,1,0,0,0,592,593,1,0,0,0,593,594,1,0,0,0,594,595,6,21,0,0,595, - 59,1,0,0,0,596,597,5,47,0,0,597,598,5,47,0,0,598,602,1,0,0,0,599,601,8, - 22,0,0,600,599,1,0,0,0,601,604,1,0,0,0,602,600,1,0,0,0,602,603,1,0,0,0, - 603,606,1,0,0,0,604,602,1,0,0,0,605,607,5,13,0,0,606,605,1,0,0,0,606,607, - 1,0,0,0,607,609,1,0,0,0,608,610,5,10,0,0,609,608,1,0,0,0,609,610,1,0,0, - 0,610,611,1,0,0,0,611,612,6,22,11,0,612,61,1,0,0,0,613,614,5,47,0,0,614, - 615,5,42,0,0,615,620,1,0,0,0,616,619,3,62,23,0,617,619,9,0,0,0,618,616, - 1,0,0,0,618,617,1,0,0,0,619,622,1,0,0,0,620,621,1,0,0,0,620,618,1,0,0,0, - 621,623,1,0,0,0,622,620,1,0,0,0,623,624,5,42,0,0,624,625,5,47,0,0,625,626, - 1,0,0,0,626,627,6,23,11,0,627,63,1,0,0,0,628,630,7,23,0,0,629,628,1,0,0, - 0,630,631,1,0,0,0,631,629,1,0,0,0,631,632,1,0,0,0,632,633,1,0,0,0,633,634, - 6,24,11,0,634,65,1,0,0,0,635,636,5,124,0,0,636,637,1,0,0,0,637,638,6,25, - 12,0,638,67,1,0,0,0,639,640,7,24,0,0,640,69,1,0,0,0,641,642,7,25,0,0,642, - 71,1,0,0,0,643,644,5,92,0,0,644,645,7,26,0,0,645,73,1,0,0,0,646,647,8,27, - 0,0,647,75,1,0,0,0,648,650,7,3,0,0,649,651,7,28,0,0,650,649,1,0,0,0,650, - 651,1,0,0,0,651,653,1,0,0,0,652,654,3,68,26,0,653,652,1,0,0,0,654,655,1, - 0,0,0,655,653,1,0,0,0,655,656,1,0,0,0,656,77,1,0,0,0,657,658,5,64,0,0,658, - 79,1,0,0,0,659,660,5,96,0,0,660,81,1,0,0,0,661,665,8,29,0,0,662,663,5,96, - 0,0,663,665,5,96,0,0,664,661,1,0,0,0,664,662,1,0,0,0,665,83,1,0,0,0,666, - 667,5,95,0,0,667,85,1,0,0,0,668,672,3,70,27,0,669,672,3,68,26,0,670,672, - 3,84,34,0,671,668,1,0,0,0,671,669,1,0,0,0,671,670,1,0,0,0,672,87,1,0,0, - 0,673,678,5,34,0,0,674,677,3,72,28,0,675,677,3,74,29,0,676,674,1,0,0,0, - 676,675,1,0,0,0,677,680,1,0,0,0,678,676,1,0,0,0,678,679,1,0,0,0,679,681, - 1,0,0,0,680,678,1,0,0,0,681,703,5,34,0,0,682,683,5,34,0,0,683,684,5,34, - 0,0,684,685,5,34,0,0,685,689,1,0,0,0,686,688,8,22,0,0,687,686,1,0,0,0,688, - 691,1,0,0,0,689,690,1,0,0,0,689,687,1,0,0,0,690,692,1,0,0,0,691,689,1,0, - 0,0,692,693,5,34,0,0,693,694,5,34,0,0,694,695,5,34,0,0,695,697,1,0,0,0, - 696,698,5,34,0,0,697,696,1,0,0,0,697,698,1,0,0,0,698,700,1,0,0,0,699,701, - 5,34,0,0,700,699,1,0,0,0,700,701,1,0,0,0,701,703,1,0,0,0,702,673,1,0,0, - 0,702,682,1,0,0,0,703,89,1,0,0,0,704,706,3,68,26,0,705,704,1,0,0,0,706, - 707,1,0,0,0,707,705,1,0,0,0,707,708,1,0,0,0,708,91,1,0,0,0,709,711,3,68, - 26,0,710,709,1,0,0,0,711,712,1,0,0,0,712,710,1,0,0,0,712,713,1,0,0,0,713, - 714,1,0,0,0,714,718,3,108,46,0,715,717,3,68,26,0,716,715,1,0,0,0,717,720, - 1,0,0,0,718,716,1,0,0,0,718,719,1,0,0,0,719,752,1,0,0,0,720,718,1,0,0,0, - 721,723,3,108,46,0,722,724,3,68,26,0,723,722,1,0,0,0,724,725,1,0,0,0,725, - 723,1,0,0,0,725,726,1,0,0,0,726,752,1,0,0,0,727,729,3,68,26,0,728,727,1, - 0,0,0,729,730,1,0,0,0,730,728,1,0,0,0,730,731,1,0,0,0,731,739,1,0,0,0,732, - 736,3,108,46,0,733,735,3,68,26,0,734,733,1,0,0,0,735,738,1,0,0,0,736,734, - 1,0,0,0,736,737,1,0,0,0,737,740,1,0,0,0,738,736,1,0,0,0,739,732,1,0,0,0, - 739,740,1,0,0,0,740,741,1,0,0,0,741,742,3,76,30,0,742,752,1,0,0,0,743,745, - 3,108,46,0,744,746,3,68,26,0,745,744,1,0,0,0,746,747,1,0,0,0,747,745,1, - 0,0,0,747,748,1,0,0,0,748,749,1,0,0,0,749,750,3,76,30,0,750,752,1,0,0,0, - 751,710,1,0,0,0,751,721,1,0,0,0,751,728,1,0,0,0,751,743,1,0,0,0,752,93, - 1,0,0,0,753,754,7,30,0,0,754,755,7,31,0,0,755,95,1,0,0,0,756,757,7,12,0, - 0,757,758,7,9,0,0,758,759,7,0,0,0,759,97,1,0,0,0,760,761,7,12,0,0,761,762, - 7,2,0,0,762,763,7,4,0,0,763,99,1,0,0,0,764,765,5,61,0,0,765,101,1,0,0,0, - 766,767,5,58,0,0,767,768,5,58,0,0,768,103,1,0,0,0,769,770,5,44,0,0,770, - 105,1,0,0,0,771,772,7,0,0,0,772,773,7,3,0,0,773,774,7,2,0,0,774,775,7,4, - 0,0,775,107,1,0,0,0,776,777,5,46,0,0,777,109,1,0,0,0,778,779,7,15,0,0,779, - 780,7,12,0,0,780,781,7,13,0,0,781,782,7,2,0,0,782,783,7,3,0,0,783,111,1, - 0,0,0,784,785,7,15,0,0,785,786,7,1,0,0,786,787,7,6,0,0,787,788,7,2,0,0, - 788,789,7,5,0,0,789,113,1,0,0,0,790,791,7,1,0,0,791,792,7,9,0,0,792,115, - 1,0,0,0,793,794,7,1,0,0,794,795,7,2,0,0,795,117,1,0,0,0,796,797,7,13,0, - 0,797,798,7,12,0,0,798,799,7,2,0,0,799,800,7,5,0,0,800,119,1,0,0,0,801, - 802,7,13,0,0,802,803,7,1,0,0,803,804,7,18,0,0,804,805,7,3,0,0,805,121,1, - 0,0,0,806,807,5,40,0,0,807,123,1,0,0,0,808,809,7,9,0,0,809,810,7,7,0,0, - 810,811,7,5,0,0,811,125,1,0,0,0,812,813,7,9,0,0,813,814,7,20,0,0,814,815, - 7,13,0,0,815,816,7,13,0,0,816,127,1,0,0,0,817,818,7,9,0,0,818,819,7,20, - 0,0,819,820,7,13,0,0,820,821,7,13,0,0,821,822,7,2,0,0,822,129,1,0,0,0,823, - 824,7,7,0,0,824,825,7,6,0,0,825,131,1,0,0,0,826,827,5,63,0,0,827,133,1, - 0,0,0,828,829,7,6,0,0,829,830,7,13,0,0,830,831,7,1,0,0,831,832,7,18,0,0, - 832,833,7,3,0,0,833,135,1,0,0,0,834,835,5,41,0,0,835,137,1,0,0,0,836,837, - 7,5,0,0,837,838,7,6,0,0,838,839,7,20,0,0,839,840,7,3,0,0,840,139,1,0,0, - 0,841,842,5,61,0,0,842,843,5,61,0,0,843,141,1,0,0,0,844,845,5,61,0,0,845, - 846,5,126,0,0,846,143,1,0,0,0,847,848,5,33,0,0,848,849,5,61,0,0,849,145, - 1,0,0,0,850,851,5,60,0,0,851,147,1,0,0,0,852,853,5,60,0,0,853,854,5,61, - 0,0,854,149,1,0,0,0,855,856,5,62,0,0,856,151,1,0,0,0,857,858,5,62,0,0,858, - 859,5,61,0,0,859,153,1,0,0,0,860,861,5,43,0,0,861,155,1,0,0,0,862,863,5, - 45,0,0,863,157,1,0,0,0,864,865,5,42,0,0,865,159,1,0,0,0,866,867,5,47,0, - 0,867,161,1,0,0,0,868,869,5,37,0,0,869,163,1,0,0,0,870,871,4,74,4,0,871, - 872,3,54,19,0,872,873,1,0,0,0,873,874,6,74,13,0,874,165,1,0,0,0,875,878, - 3,132,58,0,876,879,3,70,27,0,877,879,3,84,34,0,878,876,1,0,0,0,878,877, - 1,0,0,0,879,883,1,0,0,0,880,882,3,86,35,0,881,880,1,0,0,0,882,885,1,0,0, - 0,883,881,1,0,0,0,883,884,1,0,0,0,884,893,1,0,0,0,885,883,1,0,0,0,886,888, - 3,132,58,0,887,889,3,68,26,0,888,887,1,0,0,0,889,890,1,0,0,0,890,888,1, - 0,0,0,890,891,1,0,0,0,891,893,1,0,0,0,892,875,1,0,0,0,892,886,1,0,0,0,893, - 167,1,0,0,0,894,895,5,91,0,0,895,896,1,0,0,0,896,897,6,76,0,0,897,898,6, - 76,0,0,898,169,1,0,0,0,899,900,5,93,0,0,900,901,1,0,0,0,901,902,6,77,12, - 0,902,903,6,77,12,0,903,171,1,0,0,0,904,908,3,70,27,0,905,907,3,86,35,0, - 906,905,1,0,0,0,907,910,1,0,0,0,908,906,1,0,0,0,908,909,1,0,0,0,909,921, - 1,0,0,0,910,908,1,0,0,0,911,914,3,84,34,0,912,914,3,78,31,0,913,911,1,0, - 0,0,913,912,1,0,0,0,914,916,1,0,0,0,915,917,3,86,35,0,916,915,1,0,0,0,917, - 918,1,0,0,0,918,916,1,0,0,0,918,919,1,0,0,0,919,921,1,0,0,0,920,904,1,0, - 0,0,920,913,1,0,0,0,921,173,1,0,0,0,922,924,3,80,32,0,923,925,3,82,33,0, - 924,923,1,0,0,0,925,926,1,0,0,0,926,924,1,0,0,0,926,927,1,0,0,0,927,928, - 1,0,0,0,928,929,3,80,32,0,929,175,1,0,0,0,930,931,3,174,79,0,931,177,1, - 0,0,0,932,933,3,60,22,0,933,934,1,0,0,0,934,935,6,81,11,0,935,179,1,0,0, - 0,936,937,3,62,23,0,937,938,1,0,0,0,938,939,6,82,11,0,939,181,1,0,0,0,940, - 941,3,64,24,0,941,942,1,0,0,0,942,943,6,83,11,0,943,183,1,0,0,0,944,945, - 3,168,76,0,945,946,1,0,0,0,946,947,6,84,14,0,947,948,6,84,15,0,948,185, - 1,0,0,0,949,950,3,66,25,0,950,951,1,0,0,0,951,952,6,85,16,0,952,953,6,85, - 12,0,953,187,1,0,0,0,954,955,3,64,24,0,955,956,1,0,0,0,956,957,6,86,11, - 0,957,189,1,0,0,0,958,959,3,60,22,0,959,960,1,0,0,0,960,961,6,87,11,0,961, - 191,1,0,0,0,962,963,3,62,23,0,963,964,1,0,0,0,964,965,6,88,11,0,965,193, - 1,0,0,0,966,967,3,66,25,0,967,968,1,0,0,0,968,969,6,89,16,0,969,970,6,89, - 12,0,970,195,1,0,0,0,971,972,3,168,76,0,972,973,1,0,0,0,973,974,6,90,14, - 0,974,197,1,0,0,0,975,976,3,170,77,0,976,977,1,0,0,0,977,978,6,91,17,0, - 978,199,1,0,0,0,979,980,3,334,159,0,980,981,1,0,0,0,981,982,6,92,18,0,982, - 201,1,0,0,0,983,984,3,104,44,0,984,985,1,0,0,0,985,986,6,93,19,0,986,203, - 1,0,0,0,987,988,3,100,42,0,988,989,1,0,0,0,989,990,6,94,20,0,990,205,1, - 0,0,0,991,992,7,16,0,0,992,993,7,3,0,0,993,994,7,5,0,0,994,995,7,12,0,0, - 995,996,7,0,0,0,996,997,7,12,0,0,997,998,7,5,0,0,998,999,7,12,0,0,999,207, - 1,0,0,0,1000,1004,8,32,0,0,1001,1002,5,47,0,0,1002,1004,8,33,0,0,1003,1000, - 1,0,0,0,1003,1001,1,0,0,0,1004,209,1,0,0,0,1005,1007,3,208,96,0,1006,1005, - 1,0,0,0,1007,1008,1,0,0,0,1008,1006,1,0,0,0,1008,1009,1,0,0,0,1009,211, - 1,0,0,0,1010,1011,3,210,97,0,1011,1012,1,0,0,0,1012,1013,6,98,21,0,1013, - 213,1,0,0,0,1014,1015,3,88,36,0,1015,1016,1,0,0,0,1016,1017,6,99,22,0,1017, - 215,1,0,0,0,1018,1019,3,60,22,0,1019,1020,1,0,0,0,1020,1021,6,100,11,0, - 1021,217,1,0,0,0,1022,1023,3,62,23,0,1023,1024,1,0,0,0,1024,1025,6,101, - 11,0,1025,219,1,0,0,0,1026,1027,3,64,24,0,1027,1028,1,0,0,0,1028,1029,6, - 102,11,0,1029,221,1,0,0,0,1030,1031,3,66,25,0,1031,1032,1,0,0,0,1032,1033, - 6,103,16,0,1033,1034,6,103,12,0,1034,223,1,0,0,0,1035,1036,3,108,46,0,1036, - 1037,1,0,0,0,1037,1038,6,104,23,0,1038,225,1,0,0,0,1039,1040,3,104,44,0, - 1040,1041,1,0,0,0,1041,1042,6,105,19,0,1042,227,1,0,0,0,1043,1048,3,70, - 27,0,1044,1048,3,68,26,0,1045,1048,3,84,34,0,1046,1048,3,158,71,0,1047, - 1043,1,0,0,0,1047,1044,1,0,0,0,1047,1045,1,0,0,0,1047,1046,1,0,0,0,1048, - 229,1,0,0,0,1049,1052,3,70,27,0,1050,1052,3,158,71,0,1051,1049,1,0,0,0, - 1051,1050,1,0,0,0,1052,1056,1,0,0,0,1053,1055,3,228,106,0,1054,1053,1,0, - 0,0,1055,1058,1,0,0,0,1056,1054,1,0,0,0,1056,1057,1,0,0,0,1057,1069,1,0, - 0,0,1058,1056,1,0,0,0,1059,1062,3,84,34,0,1060,1062,3,78,31,0,1061,1059, - 1,0,0,0,1061,1060,1,0,0,0,1062,1064,1,0,0,0,1063,1065,3,228,106,0,1064, - 1063,1,0,0,0,1065,1066,1,0,0,0,1066,1064,1,0,0,0,1066,1067,1,0,0,0,1067, - 1069,1,0,0,0,1068,1051,1,0,0,0,1068,1061,1,0,0,0,1069,231,1,0,0,0,1070, - 1073,3,230,107,0,1071,1073,3,174,79,0,1072,1070,1,0,0,0,1072,1071,1,0,0, - 0,1073,1074,1,0,0,0,1074,1072,1,0,0,0,1074,1075,1,0,0,0,1075,233,1,0,0, - 0,1076,1077,3,60,22,0,1077,1078,1,0,0,0,1078,1079,6,109,11,0,1079,235,1, - 0,0,0,1080,1081,3,62,23,0,1081,1082,1,0,0,0,1082,1083,6,110,11,0,1083,237, - 1,0,0,0,1084,1085,3,64,24,0,1085,1086,1,0,0,0,1086,1087,6,111,11,0,1087, - 239,1,0,0,0,1088,1089,3,66,25,0,1089,1090,1,0,0,0,1090,1091,6,112,16,0, - 1091,1092,6,112,12,0,1092,241,1,0,0,0,1093,1094,3,100,42,0,1094,1095,1, - 0,0,0,1095,1096,6,113,20,0,1096,243,1,0,0,0,1097,1098,3,104,44,0,1098,1099, - 1,0,0,0,1099,1100,6,114,19,0,1100,245,1,0,0,0,1101,1102,3,108,46,0,1102, - 1103,1,0,0,0,1103,1104,6,115,23,0,1104,247,1,0,0,0,1105,1106,7,12,0,0,1106, - 1107,7,2,0,0,1107,249,1,0,0,0,1108,1109,3,232,108,0,1109,1110,1,0,0,0,1110, - 1111,6,117,24,0,1111,251,1,0,0,0,1112,1113,3,60,22,0,1113,1114,1,0,0,0, - 1114,1115,6,118,11,0,1115,253,1,0,0,0,1116,1117,3,62,23,0,1117,1118,1,0, - 0,0,1118,1119,6,119,11,0,1119,255,1,0,0,0,1120,1121,3,64,24,0,1121,1122, - 1,0,0,0,1122,1123,6,120,11,0,1123,257,1,0,0,0,1124,1125,3,66,25,0,1125, - 1126,1,0,0,0,1126,1127,6,121,16,0,1127,1128,6,121,12,0,1128,259,1,0,0,0, - 1129,1130,3,168,76,0,1130,1131,1,0,0,0,1131,1132,6,122,14,0,1132,1133,6, - 122,25,0,1133,261,1,0,0,0,1134,1135,7,7,0,0,1135,1136,7,9,0,0,1136,1137, - 1,0,0,0,1137,1138,6,123,26,0,1138,263,1,0,0,0,1139,1140,7,19,0,0,1140,1141, - 7,1,0,0,1141,1142,7,5,0,0,1142,1143,7,10,0,0,1143,1144,1,0,0,0,1144,1145, - 6,124,26,0,1145,265,1,0,0,0,1146,1147,8,34,0,0,1147,267,1,0,0,0,1148,1150, - 3,266,125,0,1149,1148,1,0,0,0,1150,1151,1,0,0,0,1151,1149,1,0,0,0,1151, - 1152,1,0,0,0,1152,1153,1,0,0,0,1153,1154,3,334,159,0,1154,1156,1,0,0,0, - 1155,1149,1,0,0,0,1155,1156,1,0,0,0,1156,1158,1,0,0,0,1157,1159,3,266,125, - 0,1158,1157,1,0,0,0,1159,1160,1,0,0,0,1160,1158,1,0,0,0,1160,1161,1,0,0, - 0,1161,269,1,0,0,0,1162,1163,3,268,126,0,1163,1164,1,0,0,0,1164,1165,6, - 127,27,0,1165,271,1,0,0,0,1166,1167,3,60,22,0,1167,1168,1,0,0,0,1168,1169, - 6,128,11,0,1169,273,1,0,0,0,1170,1171,3,62,23,0,1171,1172,1,0,0,0,1172, - 1173,6,129,11,0,1173,275,1,0,0,0,1174,1175,3,64,24,0,1175,1176,1,0,0,0, - 1176,1177,6,130,11,0,1177,277,1,0,0,0,1178,1179,3,66,25,0,1179,1180,1,0, - 0,0,1180,1181,6,131,16,0,1181,1182,6,131,12,0,1182,1183,6,131,12,0,1183, - 279,1,0,0,0,1184,1185,3,100,42,0,1185,1186,1,0,0,0,1186,1187,6,132,20,0, - 1187,281,1,0,0,0,1188,1189,3,104,44,0,1189,1190,1,0,0,0,1190,1191,6,133, - 19,0,1191,283,1,0,0,0,1192,1193,3,108,46,0,1193,1194,1,0,0,0,1194,1195, - 6,134,23,0,1195,285,1,0,0,0,1196,1197,3,264,124,0,1197,1198,1,0,0,0,1198, - 1199,6,135,28,0,1199,287,1,0,0,0,1200,1201,3,232,108,0,1201,1202,1,0,0, - 0,1202,1203,6,136,24,0,1203,289,1,0,0,0,1204,1205,3,176,80,0,1205,1206, - 1,0,0,0,1206,1207,6,137,29,0,1207,291,1,0,0,0,1208,1209,3,60,22,0,1209, - 1210,1,0,0,0,1210,1211,6,138,11,0,1211,293,1,0,0,0,1212,1213,3,62,23,0, - 1213,1214,1,0,0,0,1214,1215,6,139,11,0,1215,295,1,0,0,0,1216,1217,3,64, - 24,0,1217,1218,1,0,0,0,1218,1219,6,140,11,0,1219,297,1,0,0,0,1220,1221, - 3,66,25,0,1221,1222,1,0,0,0,1222,1223,6,141,16,0,1223,1224,6,141,12,0,1224, - 299,1,0,0,0,1225,1226,3,108,46,0,1226,1227,1,0,0,0,1227,1228,6,142,23,0, - 1228,301,1,0,0,0,1229,1230,3,176,80,0,1230,1231,1,0,0,0,1231,1232,6,143, - 29,0,1232,303,1,0,0,0,1233,1234,3,172,78,0,1234,1235,1,0,0,0,1235,1236, - 6,144,30,0,1236,305,1,0,0,0,1237,1238,3,60,22,0,1238,1239,1,0,0,0,1239, - 1240,6,145,11,0,1240,307,1,0,0,0,1241,1242,3,62,23,0,1242,1243,1,0,0,0, - 1243,1244,6,146,11,0,1244,309,1,0,0,0,1245,1246,3,64,24,0,1246,1247,1,0, - 0,0,1247,1248,6,147,11,0,1248,311,1,0,0,0,1249,1250,3,66,25,0,1250,1251, - 1,0,0,0,1251,1252,6,148,16,0,1252,1253,6,148,12,0,1253,313,1,0,0,0,1254, - 1255,7,1,0,0,1255,1256,7,9,0,0,1256,1257,7,15,0,0,1257,1258,7,7,0,0,1258, - 315,1,0,0,0,1259,1260,3,60,22,0,1260,1261,1,0,0,0,1261,1262,6,150,11,0, - 1262,317,1,0,0,0,1263,1264,3,62,23,0,1264,1265,1,0,0,0,1265,1266,6,151, - 11,0,1266,319,1,0,0,0,1267,1268,3,64,24,0,1268,1269,1,0,0,0,1269,1270,6, - 152,11,0,1270,321,1,0,0,0,1271,1272,3,66,25,0,1272,1273,1,0,0,0,1273,1274, - 6,153,16,0,1274,1275,6,153,12,0,1275,323,1,0,0,0,1276,1277,7,15,0,0,1277, - 1278,7,20,0,0,1278,1279,7,9,0,0,1279,1280,7,4,0,0,1280,1281,7,5,0,0,1281, - 1282,7,1,0,0,1282,1283,7,7,0,0,1283,1284,7,9,0,0,1284,1285,7,2,0,0,1285, - 325,1,0,0,0,1286,1287,3,60,22,0,1287,1288,1,0,0,0,1288,1289,6,155,11,0, - 1289,327,1,0,0,0,1290,1291,3,62,23,0,1291,1292,1,0,0,0,1292,1293,6,156, - 11,0,1293,329,1,0,0,0,1294,1295,3,64,24,0,1295,1296,1,0,0,0,1296,1297,6, - 157,11,0,1297,331,1,0,0,0,1298,1299,3,170,77,0,1299,1300,1,0,0,0,1300,1301, - 6,158,17,0,1301,1302,6,158,12,0,1302,333,1,0,0,0,1303,1304,5,58,0,0,1304, - 335,1,0,0,0,1305,1311,3,78,31,0,1306,1311,3,68,26,0,1307,1311,3,108,46, - 0,1308,1311,3,70,27,0,1309,1311,3,84,34,0,1310,1305,1,0,0,0,1310,1306,1, - 0,0,0,1310,1307,1,0,0,0,1310,1308,1,0,0,0,1310,1309,1,0,0,0,1311,1312,1, - 0,0,0,1312,1310,1,0,0,0,1312,1313,1,0,0,0,1313,337,1,0,0,0,1314,1315,3, - 60,22,0,1315,1316,1,0,0,0,1316,1317,6,161,11,0,1317,339,1,0,0,0,1318,1319, - 3,62,23,0,1319,1320,1,0,0,0,1320,1321,6,162,11,0,1321,341,1,0,0,0,1322, - 1323,3,64,24,0,1323,1324,1,0,0,0,1324,1325,6,163,11,0,1325,343,1,0,0,0, - 1326,1327,3,66,25,0,1327,1328,1,0,0,0,1328,1329,6,164,16,0,1329,1330,6, - 164,12,0,1330,345,1,0,0,0,1331,1332,3,334,159,0,1332,1333,1,0,0,0,1333, - 1334,6,165,18,0,1334,347,1,0,0,0,1335,1336,3,104,44,0,1336,1337,1,0,0,0, - 1337,1338,6,166,19,0,1338,349,1,0,0,0,1339,1340,3,108,46,0,1340,1341,1, - 0,0,0,1341,1342,6,167,23,0,1342,351,1,0,0,0,1343,1344,3,262,123,0,1344, - 1345,1,0,0,0,1345,1346,6,168,31,0,1346,1347,6,168,32,0,1347,353,1,0,0,0, - 1348,1349,3,210,97,0,1349,1350,1,0,0,0,1350,1351,6,169,21,0,1351,355,1, - 0,0,0,1352,1353,3,88,36,0,1353,1354,1,0,0,0,1354,1355,6,170,22,0,1355,357, - 1,0,0,0,1356,1357,3,60,22,0,1357,1358,1,0,0,0,1358,1359,6,171,11,0,1359, - 359,1,0,0,0,1360,1361,3,62,23,0,1361,1362,1,0,0,0,1362,1363,6,172,11,0, - 1363,361,1,0,0,0,1364,1365,3,64,24,0,1365,1366,1,0,0,0,1366,1367,6,173, - 11,0,1367,363,1,0,0,0,1368,1369,3,66,25,0,1369,1370,1,0,0,0,1370,1371,6, - 174,16,0,1371,1372,6,174,12,0,1372,1373,6,174,12,0,1373,365,1,0,0,0,1374, - 1375,3,104,44,0,1375,1376,1,0,0,0,1376,1377,6,175,19,0,1377,367,1,0,0,0, - 1378,1379,3,108,46,0,1379,1380,1,0,0,0,1380,1381,6,176,23,0,1381,369,1, - 0,0,0,1382,1383,3,232,108,0,1383,1384,1,0,0,0,1384,1385,6,177,24,0,1385, - 371,1,0,0,0,1386,1387,3,60,22,0,1387,1388,1,0,0,0,1388,1389,6,178,11,0, - 1389,373,1,0,0,0,1390,1391,3,62,23,0,1391,1392,1,0,0,0,1392,1393,6,179, - 11,0,1393,375,1,0,0,0,1394,1395,3,64,24,0,1395,1396,1,0,0,0,1396,1397,6, - 180,11,0,1397,377,1,0,0,0,1398,1399,3,66,25,0,1399,1400,1,0,0,0,1400,1401, - 6,181,16,0,1401,1402,6,181,12,0,1402,379,1,0,0,0,1403,1404,3,210,97,0,1404, - 1405,1,0,0,0,1405,1406,6,182,21,0,1406,1407,6,182,12,0,1407,1408,6,182, - 33,0,1408,381,1,0,0,0,1409,1410,3,88,36,0,1410,1411,1,0,0,0,1411,1412,6, - 183,22,0,1412,1413,6,183,12,0,1413,1414,6,183,33,0,1414,383,1,0,0,0,1415, - 1416,3,60,22,0,1416,1417,1,0,0,0,1417,1418,6,184,11,0,1418,385,1,0,0,0, - 1419,1420,3,62,23,0,1420,1421,1,0,0,0,1421,1422,6,185,11,0,1422,387,1,0, - 0,0,1423,1424,3,64,24,0,1424,1425,1,0,0,0,1425,1426,6,186,11,0,1426,389, - 1,0,0,0,1427,1428,3,334,159,0,1428,1429,1,0,0,0,1429,1430,6,187,18,0,1430, - 1431,6,187,12,0,1431,1432,6,187,10,0,1432,391,1,0,0,0,1433,1434,3,104,44, - 0,1434,1435,1,0,0,0,1435,1436,6,188,19,0,1436,1437,6,188,12,0,1437,1438, - 6,188,10,0,1438,393,1,0,0,0,1439,1440,3,60,22,0,1440,1441,1,0,0,0,1441, - 1442,6,189,11,0,1442,395,1,0,0,0,1443,1444,3,62,23,0,1444,1445,1,0,0,0, - 1445,1446,6,190,11,0,1446,397,1,0,0,0,1447,1448,3,64,24,0,1448,1449,1,0, - 0,0,1449,1450,6,191,11,0,1450,399,1,0,0,0,1451,1452,3,176,80,0,1452,1453, - 1,0,0,0,1453,1454,6,192,12,0,1454,1455,6,192,0,0,1455,1456,6,192,29,0,1456, - 401,1,0,0,0,1457,1458,3,172,78,0,1458,1459,1,0,0,0,1459,1460,6,193,12,0, - 1460,1461,6,193,0,0,1461,1462,6,193,30,0,1462,403,1,0,0,0,1463,1464,3,94, - 39,0,1464,1465,1,0,0,0,1465,1466,6,194,12,0,1466,1467,6,194,0,0,1467,1468, - 6,194,34,0,1468,405,1,0,0,0,1469,1470,3,66,25,0,1470,1471,1,0,0,0,1471, - 1472,6,195,16,0,1472,1473,6,195,12,0,1473,407,1,0,0,0,66,0,1,2,3,4,5,6, - 7,8,9,10,11,12,13,14,15,592,602,606,609,618,620,631,650,655,664,671,676, - 678,689,697,700,702,707,712,718,725,730,736,739,747,751,878,883,890,892, - 908,913,918,920,926,1003,1008,1047,1051,1056,1061,1066,1068,1072,1074,1151, - 1155,1160,1310,1312,35,5,1,0,5,4,0,5,6,0,5,2,0,5,3,0,5,10,0,5,8,0,5,5,0, - 5,9,0,5,12,0,5,14,0,0,1,0,4,0,0,7,20,0,7,66,0,5,0,0,7,26,0,7,67,0,7,109, - 0,7,35,0,7,33,0,7,77,0,7,27,0,7,37,0,7,81,0,5,11,0,5,7,0,7,91,0,7,90,0, - 7,69,0,7,68,0,7,89,0,5,13,0,5,15,0,7,30,0]; + 9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62,63,92,92,124,124,1503, + 0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1, + 0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0, + 0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1, + 0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0, + 0,59,1,0,0,0,0,61,1,0,0,0,1,63,1,0,0,0,1,85,1,0,0,0,1,87,1,0,0,0,1,89,1, + 0,0,0,1,91,1,0,0,0,1,93,1,0,0,0,1,95,1,0,0,0,1,97,1,0,0,0,1,99,1,0,0,0, + 1,101,1,0,0,0,1,103,1,0,0,0,1,105,1,0,0,0,1,107,1,0,0,0,1,109,1,0,0,0,1, + 111,1,0,0,0,1,113,1,0,0,0,1,115,1,0,0,0,1,117,1,0,0,0,1,119,1,0,0,0,1,121, + 1,0,0,0,1,123,1,0,0,0,1,125,1,0,0,0,1,127,1,0,0,0,1,129,1,0,0,0,1,131,1, + 0,0,0,1,133,1,0,0,0,1,135,1,0,0,0,1,137,1,0,0,0,1,139,1,0,0,0,1,141,1,0, + 0,0,1,143,1,0,0,0,1,145,1,0,0,0,1,147,1,0,0,0,1,149,1,0,0,0,1,151,1,0,0, + 0,1,153,1,0,0,0,1,155,1,0,0,0,1,157,1,0,0,0,1,159,1,0,0,0,1,161,1,0,0,0, + 1,163,1,0,0,0,1,165,1,0,0,0,1,167,1,0,0,0,1,169,1,0,0,0,1,173,1,0,0,0,1, + 175,1,0,0,0,1,177,1,0,0,0,1,179,1,0,0,0,2,181,1,0,0,0,2,183,1,0,0,0,2,185, + 1,0,0,0,2,187,1,0,0,0,2,189,1,0,0,0,3,191,1,0,0,0,3,193,1,0,0,0,3,195,1, + 0,0,0,3,197,1,0,0,0,3,199,1,0,0,0,3,201,1,0,0,0,3,203,1,0,0,0,3,207,1,0, + 0,0,3,209,1,0,0,0,3,211,1,0,0,0,3,213,1,0,0,0,3,215,1,0,0,0,3,217,1,0,0, + 0,4,219,1,0,0,0,4,221,1,0,0,0,4,223,1,0,0,0,4,225,1,0,0,0,4,227,1,0,0,0, + 4,233,1,0,0,0,4,235,1,0,0,0,4,237,1,0,0,0,4,239,1,0,0,0,5,241,1,0,0,0,5, + 243,1,0,0,0,5,245,1,0,0,0,5,247,1,0,0,0,5,249,1,0,0,0,5,251,1,0,0,0,5,253, + 1,0,0,0,5,255,1,0,0,0,5,257,1,0,0,0,5,259,1,0,0,0,5,261,1,0,0,0,6,263,1, + 0,0,0,6,265,1,0,0,0,6,267,1,0,0,0,6,269,1,0,0,0,6,273,1,0,0,0,6,275,1,0, + 0,0,6,277,1,0,0,0,6,279,1,0,0,0,6,281,1,0,0,0,7,283,1,0,0,0,7,285,1,0,0, + 0,7,287,1,0,0,0,7,289,1,0,0,0,7,291,1,0,0,0,7,293,1,0,0,0,7,295,1,0,0,0, + 7,297,1,0,0,0,7,299,1,0,0,0,7,301,1,0,0,0,7,303,1,0,0,0,7,305,1,0,0,0,8, + 307,1,0,0,0,8,309,1,0,0,0,8,311,1,0,0,0,8,313,1,0,0,0,8,315,1,0,0,0,8,317, + 1,0,0,0,8,319,1,0,0,0,8,321,1,0,0,0,8,323,1,0,0,0,9,325,1,0,0,0,9,327,1, + 0,0,0,9,329,1,0,0,0,9,331,1,0,0,0,9,333,1,0,0,0,10,335,1,0,0,0,10,337,1, + 0,0,0,10,339,1,0,0,0,10,341,1,0,0,0,10,343,1,0,0,0,10,345,1,0,0,0,11,347, + 1,0,0,0,11,349,1,0,0,0,11,351,1,0,0,0,11,353,1,0,0,0,11,355,1,0,0,0,11, + 357,1,0,0,0,11,359,1,0,0,0,11,361,1,0,0,0,11,363,1,0,0,0,11,365,1,0,0,0, + 12,367,1,0,0,0,12,369,1,0,0,0,12,371,1,0,0,0,12,373,1,0,0,0,12,375,1,0, + 0,0,12,377,1,0,0,0,12,379,1,0,0,0,13,381,1,0,0,0,13,383,1,0,0,0,13,385, + 1,0,0,0,13,387,1,0,0,0,13,389,1,0,0,0,13,391,1,0,0,0,14,393,1,0,0,0,14, + 395,1,0,0,0,14,397,1,0,0,0,14,399,1,0,0,0,14,401,1,0,0,0,14,403,1,0,0,0, + 14,405,1,0,0,0,14,407,1,0,0,0,14,409,1,0,0,0,15,411,1,0,0,0,17,421,1,0, + 0,0,19,428,1,0,0,0,21,437,1,0,0,0,23,444,1,0,0,0,25,454,1,0,0,0,27,461, + 1,0,0,0,29,468,1,0,0,0,31,475,1,0,0,0,33,483,1,0,0,0,35,495,1,0,0,0,37, + 504,1,0,0,0,39,510,1,0,0,0,41,517,1,0,0,0,43,524,1,0,0,0,45,532,1,0,0,0, + 47,540,1,0,0,0,49,555,1,0,0,0,51,565,1,0,0,0,53,574,1,0,0,0,55,586,1,0, + 0,0,57,592,1,0,0,0,59,609,1,0,0,0,61,625,1,0,0,0,63,631,1,0,0,0,65,635, + 1,0,0,0,67,637,1,0,0,0,69,639,1,0,0,0,71,642,1,0,0,0,73,644,1,0,0,0,75, + 653,1,0,0,0,77,655,1,0,0,0,79,660,1,0,0,0,81,662,1,0,0,0,83,667,1,0,0,0, + 85,698,1,0,0,0,87,701,1,0,0,0,89,747,1,0,0,0,91,749,1,0,0,0,93,752,1,0, + 0,0,95,756,1,0,0,0,97,760,1,0,0,0,99,762,1,0,0,0,101,765,1,0,0,0,103,767, + 1,0,0,0,105,772,1,0,0,0,107,774,1,0,0,0,109,780,1,0,0,0,111,786,1,0,0,0, + 113,789,1,0,0,0,115,792,1,0,0,0,117,797,1,0,0,0,119,802,1,0,0,0,121,804, + 1,0,0,0,123,808,1,0,0,0,125,813,1,0,0,0,127,819,1,0,0,0,129,822,1,0,0,0, + 131,824,1,0,0,0,133,830,1,0,0,0,135,832,1,0,0,0,137,837,1,0,0,0,139,840, + 1,0,0,0,141,843,1,0,0,0,143,846,1,0,0,0,145,848,1,0,0,0,147,851,1,0,0,0, + 149,853,1,0,0,0,151,856,1,0,0,0,153,858,1,0,0,0,155,860,1,0,0,0,157,862, + 1,0,0,0,159,864,1,0,0,0,161,866,1,0,0,0,163,888,1,0,0,0,165,890,1,0,0,0, + 167,895,1,0,0,0,169,916,1,0,0,0,171,918,1,0,0,0,173,926,1,0,0,0,175,928, + 1,0,0,0,177,932,1,0,0,0,179,936,1,0,0,0,181,940,1,0,0,0,183,945,1,0,0,0, + 185,950,1,0,0,0,187,954,1,0,0,0,189,958,1,0,0,0,191,962,1,0,0,0,193,967, + 1,0,0,0,195,971,1,0,0,0,197,975,1,0,0,0,199,979,1,0,0,0,201,983,1,0,0,0, + 203,987,1,0,0,0,205,999,1,0,0,0,207,1002,1,0,0,0,209,1006,1,0,0,0,211,1010, + 1,0,0,0,213,1014,1,0,0,0,215,1018,1,0,0,0,217,1022,1,0,0,0,219,1026,1,0, + 0,0,221,1031,1,0,0,0,223,1035,1,0,0,0,225,1039,1,0,0,0,227,1043,1,0,0,0, + 229,1051,1,0,0,0,231,1072,1,0,0,0,233,1076,1,0,0,0,235,1080,1,0,0,0,237, + 1084,1,0,0,0,239,1088,1,0,0,0,241,1092,1,0,0,0,243,1097,1,0,0,0,245,1101, + 1,0,0,0,247,1105,1,0,0,0,249,1109,1,0,0,0,251,1113,1,0,0,0,253,1117,1,0, + 0,0,255,1120,1,0,0,0,257,1124,1,0,0,0,259,1128,1,0,0,0,261,1132,1,0,0,0, + 263,1136,1,0,0,0,265,1141,1,0,0,0,267,1146,1,0,0,0,269,1151,1,0,0,0,271, + 1158,1,0,0,0,273,1167,1,0,0,0,275,1174,1,0,0,0,277,1178,1,0,0,0,279,1182, + 1,0,0,0,281,1186,1,0,0,0,283,1190,1,0,0,0,285,1196,1,0,0,0,287,1200,1,0, + 0,0,289,1204,1,0,0,0,291,1208,1,0,0,0,293,1212,1,0,0,0,295,1216,1,0,0,0, + 297,1220,1,0,0,0,299,1224,1,0,0,0,301,1228,1,0,0,0,303,1232,1,0,0,0,305, + 1236,1,0,0,0,307,1240,1,0,0,0,309,1245,1,0,0,0,311,1249,1,0,0,0,313,1253, + 1,0,0,0,315,1257,1,0,0,0,317,1261,1,0,0,0,319,1265,1,0,0,0,321,1269,1,0, + 0,0,323,1273,1,0,0,0,325,1277,1,0,0,0,327,1282,1,0,0,0,329,1287,1,0,0,0, + 331,1291,1,0,0,0,333,1295,1,0,0,0,335,1299,1,0,0,0,337,1304,1,0,0,0,339, + 1311,1,0,0,0,341,1315,1,0,0,0,343,1319,1,0,0,0,345,1323,1,0,0,0,347,1327, + 1,0,0,0,349,1332,1,0,0,0,351,1336,1,0,0,0,353,1340,1,0,0,0,355,1344,1,0, + 0,0,357,1349,1,0,0,0,359,1353,1,0,0,0,361,1357,1,0,0,0,363,1361,1,0,0,0, + 365,1365,1,0,0,0,367,1369,1,0,0,0,369,1375,1,0,0,0,371,1379,1,0,0,0,373, + 1383,1,0,0,0,375,1387,1,0,0,0,377,1391,1,0,0,0,379,1395,1,0,0,0,381,1399, + 1,0,0,0,383,1404,1,0,0,0,385,1410,1,0,0,0,387,1416,1,0,0,0,389,1420,1,0, + 0,0,391,1424,1,0,0,0,393,1428,1,0,0,0,395,1434,1,0,0,0,397,1440,1,0,0,0, + 399,1444,1,0,0,0,401,1448,1,0,0,0,403,1452,1,0,0,0,405,1458,1,0,0,0,407, + 1464,1,0,0,0,409,1470,1,0,0,0,411,412,7,0,0,0,412,413,7,1,0,0,413,414,7, + 2,0,0,414,415,7,2,0,0,415,416,7,3,0,0,416,417,7,4,0,0,417,418,7,5,0,0,418, + 419,1,0,0,0,419,420,6,0,0,0,420,16,1,0,0,0,421,422,7,0,0,0,422,423,7,6, + 0,0,423,424,7,7,0,0,424,425,7,8,0,0,425,426,1,0,0,0,426,427,6,1,1,0,427, + 18,1,0,0,0,428,429,7,3,0,0,429,430,7,9,0,0,430,431,7,6,0,0,431,432,7,1, + 0,0,432,433,7,4,0,0,433,434,7,10,0,0,434,435,1,0,0,0,435,436,6,2,2,0,436, + 20,1,0,0,0,437,438,7,3,0,0,438,439,7,11,0,0,439,440,7,12,0,0,440,441,7, + 13,0,0,441,442,1,0,0,0,442,443,6,3,0,0,443,22,1,0,0,0,444,445,7,3,0,0,445, + 446,7,14,0,0,446,447,7,8,0,0,447,448,7,13,0,0,448,449,7,12,0,0,449,450, + 7,1,0,0,450,451,7,9,0,0,451,452,1,0,0,0,452,453,6,4,3,0,453,24,1,0,0,0, + 454,455,7,15,0,0,455,456,7,6,0,0,456,457,7,7,0,0,457,458,7,16,0,0,458,459, + 1,0,0,0,459,460,6,5,4,0,460,26,1,0,0,0,461,462,7,17,0,0,462,463,7,6,0,0, + 463,464,7,7,0,0,464,465,7,18,0,0,465,466,1,0,0,0,466,467,6,6,0,0,467,28, + 1,0,0,0,468,469,7,18,0,0,469,470,7,3,0,0,470,471,7,3,0,0,471,472,7,8,0, + 0,472,473,1,0,0,0,473,474,6,7,1,0,474,30,1,0,0,0,475,476,7,13,0,0,476,477, + 7,1,0,0,477,478,7,16,0,0,478,479,7,1,0,0,479,480,7,5,0,0,480,481,1,0,0, + 0,481,482,6,8,0,0,482,32,1,0,0,0,483,484,7,16,0,0,484,485,7,11,0,0,485, + 486,5,95,0,0,486,487,7,3,0,0,487,488,7,14,0,0,488,489,7,8,0,0,489,490,7, + 12,0,0,490,491,7,9,0,0,491,492,7,0,0,0,492,493,1,0,0,0,493,494,6,9,5,0, + 494,34,1,0,0,0,495,496,7,6,0,0,496,497,7,3,0,0,497,498,7,9,0,0,498,499, + 7,12,0,0,499,500,7,16,0,0,500,501,7,3,0,0,501,502,1,0,0,0,502,503,6,10, + 6,0,503,36,1,0,0,0,504,505,7,6,0,0,505,506,7,7,0,0,506,507,7,19,0,0,507, + 508,1,0,0,0,508,509,6,11,0,0,509,38,1,0,0,0,510,511,7,2,0,0,511,512,7,10, + 0,0,512,513,7,7,0,0,513,514,7,19,0,0,514,515,1,0,0,0,515,516,6,12,7,0,516, + 40,1,0,0,0,517,518,7,2,0,0,518,519,7,7,0,0,519,520,7,6,0,0,520,521,7,5, + 0,0,521,522,1,0,0,0,522,523,6,13,0,0,523,42,1,0,0,0,524,525,7,2,0,0,525, + 526,7,5,0,0,526,527,7,12,0,0,527,528,7,5,0,0,528,529,7,2,0,0,529,530,1, + 0,0,0,530,531,6,14,0,0,531,44,1,0,0,0,532,533,7,19,0,0,533,534,7,10,0,0, + 534,535,7,3,0,0,535,536,7,6,0,0,536,537,7,3,0,0,537,538,1,0,0,0,538,539, + 6,15,0,0,539,46,1,0,0,0,540,541,4,16,0,0,541,542,7,1,0,0,542,543,7,9,0, + 0,543,544,7,13,0,0,544,545,7,1,0,0,545,546,7,9,0,0,546,547,7,3,0,0,547, + 548,7,2,0,0,548,549,7,5,0,0,549,550,7,12,0,0,550,551,7,5,0,0,551,552,7, + 2,0,0,552,553,1,0,0,0,553,554,6,16,0,0,554,48,1,0,0,0,555,556,4,17,1,0, + 556,557,7,13,0,0,557,558,7,7,0,0,558,559,7,7,0,0,559,560,7,18,0,0,560,561, + 7,20,0,0,561,562,7,8,0,0,562,563,1,0,0,0,563,564,6,17,8,0,564,50,1,0,0, + 0,565,566,4,18,2,0,566,567,7,16,0,0,567,568,7,12,0,0,568,569,7,5,0,0,569, + 570,7,4,0,0,570,571,7,10,0,0,571,572,1,0,0,0,572,573,6,18,0,0,573,52,1, + 0,0,0,574,575,4,19,3,0,575,576,7,16,0,0,576,577,7,3,0,0,577,578,7,5,0,0, + 578,579,7,6,0,0,579,580,7,1,0,0,580,581,7,4,0,0,581,582,7,2,0,0,582,583, + 1,0,0,0,583,584,6,19,9,0,584,54,1,0,0,0,585,587,8,21,0,0,586,585,1,0,0, + 0,587,588,1,0,0,0,588,586,1,0,0,0,588,589,1,0,0,0,589,590,1,0,0,0,590,591, + 6,20,0,0,591,56,1,0,0,0,592,593,5,47,0,0,593,594,5,47,0,0,594,598,1,0,0, + 0,595,597,8,22,0,0,596,595,1,0,0,0,597,600,1,0,0,0,598,596,1,0,0,0,598, + 599,1,0,0,0,599,602,1,0,0,0,600,598,1,0,0,0,601,603,5,13,0,0,602,601,1, + 0,0,0,602,603,1,0,0,0,603,605,1,0,0,0,604,606,5,10,0,0,605,604,1,0,0,0, + 605,606,1,0,0,0,606,607,1,0,0,0,607,608,6,21,10,0,608,58,1,0,0,0,609,610, + 5,47,0,0,610,611,5,42,0,0,611,616,1,0,0,0,612,615,3,59,22,0,613,615,9,0, + 0,0,614,612,1,0,0,0,614,613,1,0,0,0,615,618,1,0,0,0,616,617,1,0,0,0,616, + 614,1,0,0,0,617,619,1,0,0,0,618,616,1,0,0,0,619,620,5,42,0,0,620,621,5, + 47,0,0,621,622,1,0,0,0,622,623,6,22,10,0,623,60,1,0,0,0,624,626,7,23,0, + 0,625,624,1,0,0,0,626,627,1,0,0,0,627,625,1,0,0,0,627,628,1,0,0,0,628,629, + 1,0,0,0,629,630,6,23,10,0,630,62,1,0,0,0,631,632,5,124,0,0,632,633,1,0, + 0,0,633,634,6,24,11,0,634,64,1,0,0,0,635,636,7,24,0,0,636,66,1,0,0,0,637, + 638,7,25,0,0,638,68,1,0,0,0,639,640,5,92,0,0,640,641,7,26,0,0,641,70,1, + 0,0,0,642,643,8,27,0,0,643,72,1,0,0,0,644,646,7,3,0,0,645,647,7,28,0,0, + 646,645,1,0,0,0,646,647,1,0,0,0,647,649,1,0,0,0,648,650,3,65,25,0,649,648, + 1,0,0,0,650,651,1,0,0,0,651,649,1,0,0,0,651,652,1,0,0,0,652,74,1,0,0,0, + 653,654,5,64,0,0,654,76,1,0,0,0,655,656,5,96,0,0,656,78,1,0,0,0,657,661, + 8,29,0,0,658,659,5,96,0,0,659,661,5,96,0,0,660,657,1,0,0,0,660,658,1,0, + 0,0,661,80,1,0,0,0,662,663,5,95,0,0,663,82,1,0,0,0,664,668,3,67,26,0,665, + 668,3,65,25,0,666,668,3,81,33,0,667,664,1,0,0,0,667,665,1,0,0,0,667,666, + 1,0,0,0,668,84,1,0,0,0,669,674,5,34,0,0,670,673,3,69,27,0,671,673,3,71, + 28,0,672,670,1,0,0,0,672,671,1,0,0,0,673,676,1,0,0,0,674,672,1,0,0,0,674, + 675,1,0,0,0,675,677,1,0,0,0,676,674,1,0,0,0,677,699,5,34,0,0,678,679,5, + 34,0,0,679,680,5,34,0,0,680,681,5,34,0,0,681,685,1,0,0,0,682,684,8,22,0, + 0,683,682,1,0,0,0,684,687,1,0,0,0,685,686,1,0,0,0,685,683,1,0,0,0,686,688, + 1,0,0,0,687,685,1,0,0,0,688,689,5,34,0,0,689,690,5,34,0,0,690,691,5,34, + 0,0,691,693,1,0,0,0,692,694,5,34,0,0,693,692,1,0,0,0,693,694,1,0,0,0,694, + 696,1,0,0,0,695,697,5,34,0,0,696,695,1,0,0,0,696,697,1,0,0,0,697,699,1, + 0,0,0,698,669,1,0,0,0,698,678,1,0,0,0,699,86,1,0,0,0,700,702,3,65,25,0, + 701,700,1,0,0,0,702,703,1,0,0,0,703,701,1,0,0,0,703,704,1,0,0,0,704,88, + 1,0,0,0,705,707,3,65,25,0,706,705,1,0,0,0,707,708,1,0,0,0,708,706,1,0,0, + 0,708,709,1,0,0,0,709,710,1,0,0,0,710,714,3,105,45,0,711,713,3,65,25,0, + 712,711,1,0,0,0,713,716,1,0,0,0,714,712,1,0,0,0,714,715,1,0,0,0,715,748, + 1,0,0,0,716,714,1,0,0,0,717,719,3,105,45,0,718,720,3,65,25,0,719,718,1, + 0,0,0,720,721,1,0,0,0,721,719,1,0,0,0,721,722,1,0,0,0,722,748,1,0,0,0,723, + 725,3,65,25,0,724,723,1,0,0,0,725,726,1,0,0,0,726,724,1,0,0,0,726,727,1, + 0,0,0,727,735,1,0,0,0,728,732,3,105,45,0,729,731,3,65,25,0,730,729,1,0, + 0,0,731,734,1,0,0,0,732,730,1,0,0,0,732,733,1,0,0,0,733,736,1,0,0,0,734, + 732,1,0,0,0,735,728,1,0,0,0,735,736,1,0,0,0,736,737,1,0,0,0,737,738,3,73, + 29,0,738,748,1,0,0,0,739,741,3,105,45,0,740,742,3,65,25,0,741,740,1,0,0, + 0,742,743,1,0,0,0,743,741,1,0,0,0,743,744,1,0,0,0,744,745,1,0,0,0,745,746, + 3,73,29,0,746,748,1,0,0,0,747,706,1,0,0,0,747,717,1,0,0,0,747,724,1,0,0, + 0,747,739,1,0,0,0,748,90,1,0,0,0,749,750,7,30,0,0,750,751,7,31,0,0,751, + 92,1,0,0,0,752,753,7,12,0,0,753,754,7,9,0,0,754,755,7,0,0,0,755,94,1,0, + 0,0,756,757,7,12,0,0,757,758,7,2,0,0,758,759,7,4,0,0,759,96,1,0,0,0,760, + 761,5,61,0,0,761,98,1,0,0,0,762,763,5,58,0,0,763,764,5,58,0,0,764,100,1, + 0,0,0,765,766,5,44,0,0,766,102,1,0,0,0,767,768,7,0,0,0,768,769,7,3,0,0, + 769,770,7,2,0,0,770,771,7,4,0,0,771,104,1,0,0,0,772,773,5,46,0,0,773,106, + 1,0,0,0,774,775,7,15,0,0,775,776,7,12,0,0,776,777,7,13,0,0,777,778,7,2, + 0,0,778,779,7,3,0,0,779,108,1,0,0,0,780,781,7,15,0,0,781,782,7,1,0,0,782, + 783,7,6,0,0,783,784,7,2,0,0,784,785,7,5,0,0,785,110,1,0,0,0,786,787,7,1, + 0,0,787,788,7,9,0,0,788,112,1,0,0,0,789,790,7,1,0,0,790,791,7,2,0,0,791, + 114,1,0,0,0,792,793,7,13,0,0,793,794,7,12,0,0,794,795,7,2,0,0,795,796,7, + 5,0,0,796,116,1,0,0,0,797,798,7,13,0,0,798,799,7,1,0,0,799,800,7,18,0,0, + 800,801,7,3,0,0,801,118,1,0,0,0,802,803,5,40,0,0,803,120,1,0,0,0,804,805, + 7,9,0,0,805,806,7,7,0,0,806,807,7,5,0,0,807,122,1,0,0,0,808,809,7,9,0,0, + 809,810,7,20,0,0,810,811,7,13,0,0,811,812,7,13,0,0,812,124,1,0,0,0,813, + 814,7,9,0,0,814,815,7,20,0,0,815,816,7,13,0,0,816,817,7,13,0,0,817,818, + 7,2,0,0,818,126,1,0,0,0,819,820,7,7,0,0,820,821,7,6,0,0,821,128,1,0,0,0, + 822,823,5,63,0,0,823,130,1,0,0,0,824,825,7,6,0,0,825,826,7,13,0,0,826,827, + 7,1,0,0,827,828,7,18,0,0,828,829,7,3,0,0,829,132,1,0,0,0,830,831,5,41,0, + 0,831,134,1,0,0,0,832,833,7,5,0,0,833,834,7,6,0,0,834,835,7,20,0,0,835, + 836,7,3,0,0,836,136,1,0,0,0,837,838,5,61,0,0,838,839,5,61,0,0,839,138,1, + 0,0,0,840,841,5,61,0,0,841,842,5,126,0,0,842,140,1,0,0,0,843,844,5,33,0, + 0,844,845,5,61,0,0,845,142,1,0,0,0,846,847,5,60,0,0,847,144,1,0,0,0,848, + 849,5,60,0,0,849,850,5,61,0,0,850,146,1,0,0,0,851,852,5,62,0,0,852,148, + 1,0,0,0,853,854,5,62,0,0,854,855,5,61,0,0,855,150,1,0,0,0,856,857,5,43, + 0,0,857,152,1,0,0,0,858,859,5,45,0,0,859,154,1,0,0,0,860,861,5,42,0,0,861, + 156,1,0,0,0,862,863,5,47,0,0,863,158,1,0,0,0,864,865,5,37,0,0,865,160,1, + 0,0,0,866,867,4,73,4,0,867,868,3,51,18,0,868,869,1,0,0,0,869,870,6,73,12, + 0,870,162,1,0,0,0,871,874,3,129,57,0,872,875,3,67,26,0,873,875,3,81,33, + 0,874,872,1,0,0,0,874,873,1,0,0,0,875,879,1,0,0,0,876,878,3,83,34,0,877, + 876,1,0,0,0,878,881,1,0,0,0,879,877,1,0,0,0,879,880,1,0,0,0,880,889,1,0, + 0,0,881,879,1,0,0,0,882,884,3,129,57,0,883,885,3,65,25,0,884,883,1,0,0, + 0,885,886,1,0,0,0,886,884,1,0,0,0,886,887,1,0,0,0,887,889,1,0,0,0,888,871, + 1,0,0,0,888,882,1,0,0,0,889,164,1,0,0,0,890,891,5,91,0,0,891,892,1,0,0, + 0,892,893,6,75,0,0,893,894,6,75,0,0,894,166,1,0,0,0,895,896,5,93,0,0,896, + 897,1,0,0,0,897,898,6,76,11,0,898,899,6,76,11,0,899,168,1,0,0,0,900,904, + 3,67,26,0,901,903,3,83,34,0,902,901,1,0,0,0,903,906,1,0,0,0,904,902,1,0, + 0,0,904,905,1,0,0,0,905,917,1,0,0,0,906,904,1,0,0,0,907,910,3,81,33,0,908, + 910,3,75,30,0,909,907,1,0,0,0,909,908,1,0,0,0,910,912,1,0,0,0,911,913,3, + 83,34,0,912,911,1,0,0,0,913,914,1,0,0,0,914,912,1,0,0,0,914,915,1,0,0,0, + 915,917,1,0,0,0,916,900,1,0,0,0,916,909,1,0,0,0,917,170,1,0,0,0,918,920, + 3,77,31,0,919,921,3,79,32,0,920,919,1,0,0,0,921,922,1,0,0,0,922,920,1,0, + 0,0,922,923,1,0,0,0,923,924,1,0,0,0,924,925,3,77,31,0,925,172,1,0,0,0,926, + 927,3,171,78,0,927,174,1,0,0,0,928,929,3,57,21,0,929,930,1,0,0,0,930,931, + 6,80,10,0,931,176,1,0,0,0,932,933,3,59,22,0,933,934,1,0,0,0,934,935,6,81, + 10,0,935,178,1,0,0,0,936,937,3,61,23,0,937,938,1,0,0,0,938,939,6,82,10, + 0,939,180,1,0,0,0,940,941,3,165,75,0,941,942,1,0,0,0,942,943,6,83,13,0, + 943,944,6,83,14,0,944,182,1,0,0,0,945,946,3,63,24,0,946,947,1,0,0,0,947, + 948,6,84,15,0,948,949,6,84,11,0,949,184,1,0,0,0,950,951,3,61,23,0,951,952, + 1,0,0,0,952,953,6,85,10,0,953,186,1,0,0,0,954,955,3,57,21,0,955,956,1,0, + 0,0,956,957,6,86,10,0,957,188,1,0,0,0,958,959,3,59,22,0,959,960,1,0,0,0, + 960,961,6,87,10,0,961,190,1,0,0,0,962,963,3,63,24,0,963,964,1,0,0,0,964, + 965,6,88,15,0,965,966,6,88,11,0,966,192,1,0,0,0,967,968,3,165,75,0,968, + 969,1,0,0,0,969,970,6,89,13,0,970,194,1,0,0,0,971,972,3,167,76,0,972,973, + 1,0,0,0,973,974,6,90,16,0,974,196,1,0,0,0,975,976,3,337,161,0,976,977,1, + 0,0,0,977,978,6,91,17,0,978,198,1,0,0,0,979,980,3,101,43,0,980,981,1,0, + 0,0,981,982,6,92,18,0,982,200,1,0,0,0,983,984,3,97,41,0,984,985,1,0,0,0, + 985,986,6,93,19,0,986,202,1,0,0,0,987,988,7,16,0,0,988,989,7,3,0,0,989, + 990,7,5,0,0,990,991,7,12,0,0,991,992,7,0,0,0,992,993,7,12,0,0,993,994,7, + 5,0,0,994,995,7,12,0,0,995,204,1,0,0,0,996,1000,8,32,0,0,997,998,5,47,0, + 0,998,1000,8,33,0,0,999,996,1,0,0,0,999,997,1,0,0,0,1000,206,1,0,0,0,1001, + 1003,3,205,95,0,1002,1001,1,0,0,0,1003,1004,1,0,0,0,1004,1002,1,0,0,0,1004, + 1005,1,0,0,0,1005,208,1,0,0,0,1006,1007,3,207,96,0,1007,1008,1,0,0,0,1008, + 1009,6,97,20,0,1009,210,1,0,0,0,1010,1011,3,85,35,0,1011,1012,1,0,0,0,1012, + 1013,6,98,21,0,1013,212,1,0,0,0,1014,1015,3,57,21,0,1015,1016,1,0,0,0,1016, + 1017,6,99,10,0,1017,214,1,0,0,0,1018,1019,3,59,22,0,1019,1020,1,0,0,0,1020, + 1021,6,100,10,0,1021,216,1,0,0,0,1022,1023,3,61,23,0,1023,1024,1,0,0,0, + 1024,1025,6,101,10,0,1025,218,1,0,0,0,1026,1027,3,63,24,0,1027,1028,1,0, + 0,0,1028,1029,6,102,15,0,1029,1030,6,102,11,0,1030,220,1,0,0,0,1031,1032, + 3,105,45,0,1032,1033,1,0,0,0,1033,1034,6,103,22,0,1034,222,1,0,0,0,1035, + 1036,3,101,43,0,1036,1037,1,0,0,0,1037,1038,6,104,18,0,1038,224,1,0,0,0, + 1039,1040,3,129,57,0,1040,1041,1,0,0,0,1041,1042,6,105,23,0,1042,226,1, + 0,0,0,1043,1044,3,163,74,0,1044,1045,1,0,0,0,1045,1046,6,106,24,0,1046, + 228,1,0,0,0,1047,1052,3,67,26,0,1048,1052,3,65,25,0,1049,1052,3,81,33,0, + 1050,1052,3,155,70,0,1051,1047,1,0,0,0,1051,1048,1,0,0,0,1051,1049,1,0, + 0,0,1051,1050,1,0,0,0,1052,230,1,0,0,0,1053,1056,3,67,26,0,1054,1056,3, + 155,70,0,1055,1053,1,0,0,0,1055,1054,1,0,0,0,1056,1060,1,0,0,0,1057,1059, + 3,229,107,0,1058,1057,1,0,0,0,1059,1062,1,0,0,0,1060,1058,1,0,0,0,1060, + 1061,1,0,0,0,1061,1073,1,0,0,0,1062,1060,1,0,0,0,1063,1066,3,81,33,0,1064, + 1066,3,75,30,0,1065,1063,1,0,0,0,1065,1064,1,0,0,0,1066,1068,1,0,0,0,1067, + 1069,3,229,107,0,1068,1067,1,0,0,0,1069,1070,1,0,0,0,1070,1068,1,0,0,0, + 1070,1071,1,0,0,0,1071,1073,1,0,0,0,1072,1055,1,0,0,0,1072,1065,1,0,0,0, + 1073,232,1,0,0,0,1074,1077,3,231,108,0,1075,1077,3,171,78,0,1076,1074,1, + 0,0,0,1076,1075,1,0,0,0,1077,1078,1,0,0,0,1078,1076,1,0,0,0,1078,1079,1, + 0,0,0,1079,234,1,0,0,0,1080,1081,3,57,21,0,1081,1082,1,0,0,0,1082,1083, + 6,110,10,0,1083,236,1,0,0,0,1084,1085,3,59,22,0,1085,1086,1,0,0,0,1086, + 1087,6,111,10,0,1087,238,1,0,0,0,1088,1089,3,61,23,0,1089,1090,1,0,0,0, + 1090,1091,6,112,10,0,1091,240,1,0,0,0,1092,1093,3,63,24,0,1093,1094,1,0, + 0,0,1094,1095,6,113,15,0,1095,1096,6,113,11,0,1096,242,1,0,0,0,1097,1098, + 3,97,41,0,1098,1099,1,0,0,0,1099,1100,6,114,19,0,1100,244,1,0,0,0,1101, + 1102,3,101,43,0,1102,1103,1,0,0,0,1103,1104,6,115,18,0,1104,246,1,0,0,0, + 1105,1106,3,105,45,0,1106,1107,1,0,0,0,1107,1108,6,116,22,0,1108,248,1, + 0,0,0,1109,1110,3,129,57,0,1110,1111,1,0,0,0,1111,1112,6,117,23,0,1112, + 250,1,0,0,0,1113,1114,3,163,74,0,1114,1115,1,0,0,0,1115,1116,6,118,24,0, + 1116,252,1,0,0,0,1117,1118,7,12,0,0,1118,1119,7,2,0,0,1119,254,1,0,0,0, + 1120,1121,3,233,109,0,1121,1122,1,0,0,0,1122,1123,6,120,25,0,1123,256,1, + 0,0,0,1124,1125,3,57,21,0,1125,1126,1,0,0,0,1126,1127,6,121,10,0,1127,258, + 1,0,0,0,1128,1129,3,59,22,0,1129,1130,1,0,0,0,1130,1131,6,122,10,0,1131, + 260,1,0,0,0,1132,1133,3,61,23,0,1133,1134,1,0,0,0,1134,1135,6,123,10,0, + 1135,262,1,0,0,0,1136,1137,3,63,24,0,1137,1138,1,0,0,0,1138,1139,6,124, + 15,0,1139,1140,6,124,11,0,1140,264,1,0,0,0,1141,1142,3,165,75,0,1142,1143, + 1,0,0,0,1143,1144,6,125,13,0,1144,1145,6,125,26,0,1145,266,1,0,0,0,1146, + 1147,7,7,0,0,1147,1148,7,9,0,0,1148,1149,1,0,0,0,1149,1150,6,126,27,0,1150, + 268,1,0,0,0,1151,1152,7,19,0,0,1152,1153,7,1,0,0,1153,1154,7,5,0,0,1154, + 1155,7,10,0,0,1155,1156,1,0,0,0,1156,1157,6,127,27,0,1157,270,1,0,0,0,1158, + 1159,8,34,0,0,1159,272,1,0,0,0,1160,1162,3,271,128,0,1161,1160,1,0,0,0, + 1162,1163,1,0,0,0,1163,1161,1,0,0,0,1163,1164,1,0,0,0,1164,1165,1,0,0,0, + 1165,1166,3,337,161,0,1166,1168,1,0,0,0,1167,1161,1,0,0,0,1167,1168,1,0, + 0,0,1168,1170,1,0,0,0,1169,1171,3,271,128,0,1170,1169,1,0,0,0,1171,1172, + 1,0,0,0,1172,1170,1,0,0,0,1172,1173,1,0,0,0,1173,274,1,0,0,0,1174,1175, + 3,273,129,0,1175,1176,1,0,0,0,1176,1177,6,130,28,0,1177,276,1,0,0,0,1178, + 1179,3,57,21,0,1179,1180,1,0,0,0,1180,1181,6,131,10,0,1181,278,1,0,0,0, + 1182,1183,3,59,22,0,1183,1184,1,0,0,0,1184,1185,6,132,10,0,1185,280,1,0, + 0,0,1186,1187,3,61,23,0,1187,1188,1,0,0,0,1188,1189,6,133,10,0,1189,282, + 1,0,0,0,1190,1191,3,63,24,0,1191,1192,1,0,0,0,1192,1193,6,134,15,0,1193, + 1194,6,134,11,0,1194,1195,6,134,11,0,1195,284,1,0,0,0,1196,1197,3,97,41, + 0,1197,1198,1,0,0,0,1198,1199,6,135,19,0,1199,286,1,0,0,0,1200,1201,3,101, + 43,0,1201,1202,1,0,0,0,1202,1203,6,136,18,0,1203,288,1,0,0,0,1204,1205, + 3,105,45,0,1205,1206,1,0,0,0,1206,1207,6,137,22,0,1207,290,1,0,0,0,1208, + 1209,3,269,127,0,1209,1210,1,0,0,0,1210,1211,6,138,29,0,1211,292,1,0,0, + 0,1212,1213,3,233,109,0,1213,1214,1,0,0,0,1214,1215,6,139,25,0,1215,294, + 1,0,0,0,1216,1217,3,173,79,0,1217,1218,1,0,0,0,1218,1219,6,140,30,0,1219, + 296,1,0,0,0,1220,1221,3,129,57,0,1221,1222,1,0,0,0,1222,1223,6,141,23,0, + 1223,298,1,0,0,0,1224,1225,3,163,74,0,1225,1226,1,0,0,0,1226,1227,6,142, + 24,0,1227,300,1,0,0,0,1228,1229,3,57,21,0,1229,1230,1,0,0,0,1230,1231,6, + 143,10,0,1231,302,1,0,0,0,1232,1233,3,59,22,0,1233,1234,1,0,0,0,1234,1235, + 6,144,10,0,1235,304,1,0,0,0,1236,1237,3,61,23,0,1237,1238,1,0,0,0,1238, + 1239,6,145,10,0,1239,306,1,0,0,0,1240,1241,3,63,24,0,1241,1242,1,0,0,0, + 1242,1243,6,146,15,0,1243,1244,6,146,11,0,1244,308,1,0,0,0,1245,1246,3, + 105,45,0,1246,1247,1,0,0,0,1247,1248,6,147,22,0,1248,310,1,0,0,0,1249,1250, + 3,129,57,0,1250,1251,1,0,0,0,1251,1252,6,148,23,0,1252,312,1,0,0,0,1253, + 1254,3,163,74,0,1254,1255,1,0,0,0,1255,1256,6,149,24,0,1256,314,1,0,0,0, + 1257,1258,3,173,79,0,1258,1259,1,0,0,0,1259,1260,6,150,30,0,1260,316,1, + 0,0,0,1261,1262,3,169,77,0,1262,1263,1,0,0,0,1263,1264,6,151,31,0,1264, + 318,1,0,0,0,1265,1266,3,57,21,0,1266,1267,1,0,0,0,1267,1268,6,152,10,0, + 1268,320,1,0,0,0,1269,1270,3,59,22,0,1270,1271,1,0,0,0,1271,1272,6,153, + 10,0,1272,322,1,0,0,0,1273,1274,3,61,23,0,1274,1275,1,0,0,0,1275,1276,6, + 154,10,0,1276,324,1,0,0,0,1277,1278,3,63,24,0,1278,1279,1,0,0,0,1279,1280, + 6,155,15,0,1280,1281,6,155,11,0,1281,326,1,0,0,0,1282,1283,7,1,0,0,1283, + 1284,7,9,0,0,1284,1285,7,15,0,0,1285,1286,7,7,0,0,1286,328,1,0,0,0,1287, + 1288,3,57,21,0,1288,1289,1,0,0,0,1289,1290,6,157,10,0,1290,330,1,0,0,0, + 1291,1292,3,59,22,0,1292,1293,1,0,0,0,1293,1294,6,158,10,0,1294,332,1,0, + 0,0,1295,1296,3,61,23,0,1296,1297,1,0,0,0,1297,1298,6,159,10,0,1298,334, + 1,0,0,0,1299,1300,3,167,76,0,1300,1301,1,0,0,0,1301,1302,6,160,16,0,1302, + 1303,6,160,11,0,1303,336,1,0,0,0,1304,1305,5,58,0,0,1305,338,1,0,0,0,1306, + 1312,3,75,30,0,1307,1312,3,65,25,0,1308,1312,3,105,45,0,1309,1312,3,67, + 26,0,1310,1312,3,81,33,0,1311,1306,1,0,0,0,1311,1307,1,0,0,0,1311,1308, + 1,0,0,0,1311,1309,1,0,0,0,1311,1310,1,0,0,0,1312,1313,1,0,0,0,1313,1311, + 1,0,0,0,1313,1314,1,0,0,0,1314,340,1,0,0,0,1315,1316,3,57,21,0,1316,1317, + 1,0,0,0,1317,1318,6,163,10,0,1318,342,1,0,0,0,1319,1320,3,59,22,0,1320, + 1321,1,0,0,0,1321,1322,6,164,10,0,1322,344,1,0,0,0,1323,1324,3,61,23,0, + 1324,1325,1,0,0,0,1325,1326,6,165,10,0,1326,346,1,0,0,0,1327,1328,3,63, + 24,0,1328,1329,1,0,0,0,1329,1330,6,166,15,0,1330,1331,6,166,11,0,1331,348, + 1,0,0,0,1332,1333,3,337,161,0,1333,1334,1,0,0,0,1334,1335,6,167,17,0,1335, + 350,1,0,0,0,1336,1337,3,101,43,0,1337,1338,1,0,0,0,1338,1339,6,168,18,0, + 1339,352,1,0,0,0,1340,1341,3,105,45,0,1341,1342,1,0,0,0,1342,1343,6,169, + 22,0,1343,354,1,0,0,0,1344,1345,3,267,126,0,1345,1346,1,0,0,0,1346,1347, + 6,170,32,0,1347,1348,6,170,33,0,1348,356,1,0,0,0,1349,1350,3,207,96,0,1350, + 1351,1,0,0,0,1351,1352,6,171,20,0,1352,358,1,0,0,0,1353,1354,3,85,35,0, + 1354,1355,1,0,0,0,1355,1356,6,172,21,0,1356,360,1,0,0,0,1357,1358,3,57, + 21,0,1358,1359,1,0,0,0,1359,1360,6,173,10,0,1360,362,1,0,0,0,1361,1362, + 3,59,22,0,1362,1363,1,0,0,0,1363,1364,6,174,10,0,1364,364,1,0,0,0,1365, + 1366,3,61,23,0,1366,1367,1,0,0,0,1367,1368,6,175,10,0,1368,366,1,0,0,0, + 1369,1370,3,63,24,0,1370,1371,1,0,0,0,1371,1372,6,176,15,0,1372,1373,6, + 176,11,0,1373,1374,6,176,11,0,1374,368,1,0,0,0,1375,1376,3,101,43,0,1376, + 1377,1,0,0,0,1377,1378,6,177,18,0,1378,370,1,0,0,0,1379,1380,3,105,45,0, + 1380,1381,1,0,0,0,1381,1382,6,178,22,0,1382,372,1,0,0,0,1383,1384,3,233, + 109,0,1384,1385,1,0,0,0,1385,1386,6,179,25,0,1386,374,1,0,0,0,1387,1388, + 3,57,21,0,1388,1389,1,0,0,0,1389,1390,6,180,10,0,1390,376,1,0,0,0,1391, + 1392,3,59,22,0,1392,1393,1,0,0,0,1393,1394,6,181,10,0,1394,378,1,0,0,0, + 1395,1396,3,61,23,0,1396,1397,1,0,0,0,1397,1398,6,182,10,0,1398,380,1,0, + 0,0,1399,1400,3,63,24,0,1400,1401,1,0,0,0,1401,1402,6,183,15,0,1402,1403, + 6,183,11,0,1403,382,1,0,0,0,1404,1405,3,207,96,0,1405,1406,1,0,0,0,1406, + 1407,6,184,20,0,1407,1408,6,184,11,0,1408,1409,6,184,34,0,1409,384,1,0, + 0,0,1410,1411,3,85,35,0,1411,1412,1,0,0,0,1412,1413,6,185,21,0,1413,1414, + 6,185,11,0,1414,1415,6,185,34,0,1415,386,1,0,0,0,1416,1417,3,57,21,0,1417, + 1418,1,0,0,0,1418,1419,6,186,10,0,1419,388,1,0,0,0,1420,1421,3,59,22,0, + 1421,1422,1,0,0,0,1422,1423,6,187,10,0,1423,390,1,0,0,0,1424,1425,3,61, + 23,0,1425,1426,1,0,0,0,1426,1427,6,188,10,0,1427,392,1,0,0,0,1428,1429, + 3,337,161,0,1429,1430,1,0,0,0,1430,1431,6,189,17,0,1431,1432,6,189,11,0, + 1432,1433,6,189,9,0,1433,394,1,0,0,0,1434,1435,3,101,43,0,1435,1436,1,0, + 0,0,1436,1437,6,190,18,0,1437,1438,6,190,11,0,1438,1439,6,190,9,0,1439, + 396,1,0,0,0,1440,1441,3,57,21,0,1441,1442,1,0,0,0,1442,1443,6,191,10,0, + 1443,398,1,0,0,0,1444,1445,3,59,22,0,1445,1446,1,0,0,0,1446,1447,6,192, + 10,0,1447,400,1,0,0,0,1448,1449,3,61,23,0,1449,1450,1,0,0,0,1450,1451,6, + 193,10,0,1451,402,1,0,0,0,1452,1453,3,173,79,0,1453,1454,1,0,0,0,1454,1455, + 6,194,11,0,1455,1456,6,194,0,0,1456,1457,6,194,30,0,1457,404,1,0,0,0,1458, + 1459,3,169,77,0,1459,1460,1,0,0,0,1460,1461,6,195,11,0,1461,1462,6,195, + 0,0,1462,1463,6,195,31,0,1463,406,1,0,0,0,1464,1465,3,91,38,0,1465,1466, + 1,0,0,0,1466,1467,6,196,11,0,1467,1468,6,196,0,0,1468,1469,6,196,35,0,1469, + 408,1,0,0,0,1470,1471,3,63,24,0,1471,1472,1,0,0,0,1472,1473,6,197,15,0, + 1473,1474,6,197,11,0,1474,410,1,0,0,0,65,0,1,2,3,4,5,6,7,8,9,10,11,12,13, + 14,588,598,602,605,614,616,627,646,651,660,667,672,674,685,693,696,698, + 703,708,714,721,726,732,735,743,747,874,879,886,888,904,909,914,916,922, + 999,1004,1051,1055,1060,1065,1070,1072,1076,1078,1163,1167,1172,1311,1313, + 36,5,1,0,5,4,0,5,6,0,5,2,0,5,3,0,5,8,0,5,5,0,5,9,0,5,11,0,5,13,0,0,1,0, + 4,0,0,7,19,0,7,65,0,5,0,0,7,25,0,7,66,0,7,104,0,7,34,0,7,32,0,7,76,0,7, + 26,0,7,36,0,7,48,0,7,64,0,7,80,0,5,10,0,5,7,0,7,90,0,7,89,0,7,68,0,7,67, + 0,7,88,0,5,12,0,5,14,0,7,29,0]; private static __ATN: ATN; public static get _ATN(): ATN { diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 index 9fbfefb0a7c75..9d52d84dcc587 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 @@ -34,7 +34,6 @@ query sourceCommand : explainCommand | fromCommand - | metaCommand | rowCommand | showCommand // in development @@ -104,7 +103,7 @@ primaryExpression ; functionExpression - : identifier LP (ASTERISK | (booleanExpression (COMMA booleanExpression)*))? RP + : identifierOrParameter LP (ASTERISK | (booleanExpression (COMMA booleanExpression)*))? RP ; dataType @@ -168,7 +167,7 @@ statsCommand ; qualifiedName - : identifier (DOT identifier)* + : identifierOrParameter (DOT identifierOrParameter)* ; qualifiedNamePattern @@ -186,6 +185,7 @@ identifier identifierPattern : ID_PATTERN + | parameter ; constant @@ -194,18 +194,23 @@ constant | decimalValue #decimalLiteral | integerValue #integerLiteral | booleanValue #booleanLiteral - | params #inputParams + | parameter #inputParameter | string #stringLiteral | OPENING_BRACKET numericValue (COMMA numericValue)* CLOSING_BRACKET #numericArrayLiteral | OPENING_BRACKET booleanValue (COMMA booleanValue)* CLOSING_BRACKET #booleanArrayLiteral | OPENING_BRACKET string (COMMA string)* CLOSING_BRACKET #stringArrayLiteral ; -params +parameter : PARAM #inputParam | NAMED_OR_POSITIONAL_PARAM #inputNamedOrPositionalParam ; +identifierOrParameter + : identifier + | parameter + ; + limitCommand : LIMIT INTEGER_LITERAL ; @@ -291,10 +296,6 @@ showCommand : SHOW INFO #showInfo ; -metaCommand - : META FUNCTIONS #metaFunctions - ; - enrichCommand : ENRICH policyName=ENRICH_POLICY_NAME (ON matchField=qualifiedNamePattern)? (WITH enrichWithClause (COMMA enrichWithClause)*)? ; diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.interp b/packages/kbn-esql-ast/src/antlr/esql_parser.interp index f7eed3e9be796..eb3c70385d628 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.interp @@ -9,7 +9,6 @@ null 'grok' 'keep' 'limit' -'meta' 'mv_expand' 'rename' 'row' @@ -104,10 +103,6 @@ null null null null -'functions' -null -null -null ':' null null @@ -137,7 +132,6 @@ FROM GROK KEEP LIMIT -META MV_EXPAND RENAME ROW @@ -232,10 +226,6 @@ INFO SHOW_LINE_COMMENT SHOW_MULTILINE_COMMENT SHOW_WS -FUNCTIONS -META_LINE_COMMENT -META_MULTILINE_COMMENT -META_WS COLON SETTING SETTING_LINE_COMMENT @@ -287,7 +277,8 @@ qualifiedNamePatterns identifier identifierPattern constant -params +parameter +identifierOrParameter limitCommand sortCommand orderExpression @@ -309,7 +300,6 @@ comparisonOperator explainCommand subqueryExpression showCommand -metaCommand enrichCommand enrichWithClause lookupCommand @@ -317,4 +307,4 @@ inlinestatsCommand atn: -[4, 1, 125, 578, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 128, 8, 1, 10, 1, 12, 1, 131, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 140, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 158, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 170, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 177, 8, 5, 10, 5, 12, 5, 180, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 187, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 193, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 201, 8, 5, 10, 5, 12, 5, 204, 9, 5, 1, 6, 1, 6, 3, 6, 208, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 215, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 220, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 231, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 237, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 245, 8, 9, 10, 9, 12, 9, 248, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 258, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 263, 8, 10, 10, 10, 12, 10, 266, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 274, 8, 11, 10, 11, 12, 11, 277, 9, 11, 3, 11, 279, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 5, 14, 291, 8, 14, 10, 14, 12, 14, 294, 9, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 301, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 307, 8, 16, 10, 16, 12, 16, 310, 9, 16, 1, 16, 3, 16, 313, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 320, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 328, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 334, 8, 21, 10, 21, 12, 21, 337, 9, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 347, 8, 23, 10, 23, 12, 23, 350, 9, 23, 1, 23, 3, 23, 353, 8, 23, 1, 23, 1, 23, 3, 23, 357, 8, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 3, 25, 364, 8, 25, 1, 25, 1, 25, 3, 25, 368, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 373, 8, 26, 10, 26, 12, 26, 376, 9, 26, 1, 27, 1, 27, 1, 27, 5, 27, 381, 8, 27, 10, 27, 12, 27, 384, 9, 27, 1, 28, 1, 28, 1, 28, 5, 28, 389, 8, 28, 10, 28, 12, 28, 392, 9, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 411, 8, 31, 10, 31, 12, 31, 414, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 422, 8, 31, 10, 31, 12, 31, 425, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 433, 8, 31, 10, 31, 12, 31, 436, 9, 31, 1, 31, 1, 31, 3, 31, 440, 8, 31, 1, 32, 1, 32, 3, 32, 444, 8, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 453, 8, 34, 10, 34, 12, 34, 456, 9, 34, 1, 35, 1, 35, 3, 35, 460, 8, 35, 1, 35, 1, 35, 3, 35, 464, 8, 35, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 476, 8, 38, 10, 38, 12, 38, 479, 9, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 3, 40, 489, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 5, 43, 501, 8, 43, 10, 43, 12, 43, 504, 9, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 3, 46, 514, 8, 46, 1, 47, 3, 47, 517, 8, 47, 1, 47, 1, 47, 1, 48, 3, 48, 522, 8, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 3, 55, 547, 8, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 553, 8, 55, 10, 55, 12, 55, 556, 9, 55, 3, 55, 558, 8, 55, 1, 56, 1, 56, 1, 56, 3, 56, 563, 8, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 576, 8, 58, 1, 58, 0, 4, 2, 10, 18, 20, 59, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 0, 8, 1, 0, 60, 61, 1, 0, 62, 64, 2, 0, 27, 27, 77, 77, 1, 0, 68, 69, 2, 0, 32, 32, 36, 36, 2, 0, 39, 39, 42, 42, 2, 0, 38, 38, 52, 52, 2, 0, 53, 53, 55, 59, 603, 0, 118, 1, 0, 0, 0, 2, 121, 1, 0, 0, 0, 4, 139, 1, 0, 0, 0, 6, 157, 1, 0, 0, 0, 8, 159, 1, 0, 0, 0, 10, 192, 1, 0, 0, 0, 12, 219, 1, 0, 0, 0, 14, 221, 1, 0, 0, 0, 16, 230, 1, 0, 0, 0, 18, 236, 1, 0, 0, 0, 20, 257, 1, 0, 0, 0, 22, 267, 1, 0, 0, 0, 24, 282, 1, 0, 0, 0, 26, 284, 1, 0, 0, 0, 28, 287, 1, 0, 0, 0, 30, 300, 1, 0, 0, 0, 32, 302, 1, 0, 0, 0, 34, 319, 1, 0, 0, 0, 36, 321, 1, 0, 0, 0, 38, 323, 1, 0, 0, 0, 40, 327, 1, 0, 0, 0, 42, 329, 1, 0, 0, 0, 44, 338, 1, 0, 0, 0, 46, 342, 1, 0, 0, 0, 48, 358, 1, 0, 0, 0, 50, 361, 1, 0, 0, 0, 52, 369, 1, 0, 0, 0, 54, 377, 1, 0, 0, 0, 56, 385, 1, 0, 0, 0, 58, 393, 1, 0, 0, 0, 60, 395, 1, 0, 0, 0, 62, 439, 1, 0, 0, 0, 64, 443, 1, 0, 0, 0, 66, 445, 1, 0, 0, 0, 68, 448, 1, 0, 0, 0, 70, 457, 1, 0, 0, 0, 72, 465, 1, 0, 0, 0, 74, 468, 1, 0, 0, 0, 76, 471, 1, 0, 0, 0, 78, 480, 1, 0, 0, 0, 80, 484, 1, 0, 0, 0, 82, 490, 1, 0, 0, 0, 84, 494, 1, 0, 0, 0, 86, 497, 1, 0, 0, 0, 88, 505, 1, 0, 0, 0, 90, 509, 1, 0, 0, 0, 92, 513, 1, 0, 0, 0, 94, 516, 1, 0, 0, 0, 96, 521, 1, 0, 0, 0, 98, 525, 1, 0, 0, 0, 100, 527, 1, 0, 0, 0, 102, 529, 1, 0, 0, 0, 104, 532, 1, 0, 0, 0, 106, 536, 1, 0, 0, 0, 108, 539, 1, 0, 0, 0, 110, 542, 1, 0, 0, 0, 112, 562, 1, 0, 0, 0, 114, 566, 1, 0, 0, 0, 116, 571, 1, 0, 0, 0, 118, 119, 3, 2, 1, 0, 119, 120, 5, 0, 0, 1, 120, 1, 1, 0, 0, 0, 121, 122, 6, 1, -1, 0, 122, 123, 3, 4, 2, 0, 123, 129, 1, 0, 0, 0, 124, 125, 10, 1, 0, 0, 125, 126, 5, 26, 0, 0, 126, 128, 3, 6, 3, 0, 127, 124, 1, 0, 0, 0, 128, 131, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 3, 1, 0, 0, 0, 131, 129, 1, 0, 0, 0, 132, 140, 3, 102, 51, 0, 133, 140, 3, 32, 16, 0, 134, 140, 3, 108, 54, 0, 135, 140, 3, 26, 13, 0, 136, 140, 3, 106, 53, 0, 137, 138, 4, 2, 1, 0, 138, 140, 3, 46, 23, 0, 139, 132, 1, 0, 0, 0, 139, 133, 1, 0, 0, 0, 139, 134, 1, 0, 0, 0, 139, 135, 1, 0, 0, 0, 139, 136, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 140, 5, 1, 0, 0, 0, 141, 158, 3, 48, 24, 0, 142, 158, 3, 8, 4, 0, 143, 158, 3, 72, 36, 0, 144, 158, 3, 66, 33, 0, 145, 158, 3, 50, 25, 0, 146, 158, 3, 68, 34, 0, 147, 158, 3, 74, 37, 0, 148, 158, 3, 76, 38, 0, 149, 158, 3, 80, 40, 0, 150, 158, 3, 82, 41, 0, 151, 158, 3, 110, 55, 0, 152, 158, 3, 84, 42, 0, 153, 154, 4, 3, 2, 0, 154, 158, 3, 116, 58, 0, 155, 156, 4, 3, 3, 0, 156, 158, 3, 114, 57, 0, 157, 141, 1, 0, 0, 0, 157, 142, 1, 0, 0, 0, 157, 143, 1, 0, 0, 0, 157, 144, 1, 0, 0, 0, 157, 145, 1, 0, 0, 0, 157, 146, 1, 0, 0, 0, 157, 147, 1, 0, 0, 0, 157, 148, 1, 0, 0, 0, 157, 149, 1, 0, 0, 0, 157, 150, 1, 0, 0, 0, 157, 151, 1, 0, 0, 0, 157, 152, 1, 0, 0, 0, 157, 153, 1, 0, 0, 0, 157, 155, 1, 0, 0, 0, 158, 7, 1, 0, 0, 0, 159, 160, 5, 17, 0, 0, 160, 161, 3, 10, 5, 0, 161, 9, 1, 0, 0, 0, 162, 163, 6, 5, -1, 0, 163, 164, 5, 45, 0, 0, 164, 193, 3, 10, 5, 8, 165, 193, 3, 16, 8, 0, 166, 193, 3, 12, 6, 0, 167, 169, 3, 16, 8, 0, 168, 170, 5, 45, 0, 0, 169, 168, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 171, 1, 0, 0, 0, 171, 172, 5, 40, 0, 0, 172, 173, 5, 44, 0, 0, 173, 178, 3, 16, 8, 0, 174, 175, 5, 35, 0, 0, 175, 177, 3, 16, 8, 0, 176, 174, 1, 0, 0, 0, 177, 180, 1, 0, 0, 0, 178, 176, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 181, 1, 0, 0, 0, 180, 178, 1, 0, 0, 0, 181, 182, 5, 51, 0, 0, 182, 193, 1, 0, 0, 0, 183, 184, 3, 16, 8, 0, 184, 186, 5, 41, 0, 0, 185, 187, 5, 45, 0, 0, 186, 185, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 1, 0, 0, 0, 188, 189, 5, 46, 0, 0, 189, 193, 1, 0, 0, 0, 190, 191, 4, 5, 4, 0, 191, 193, 3, 14, 7, 0, 192, 162, 1, 0, 0, 0, 192, 165, 1, 0, 0, 0, 192, 166, 1, 0, 0, 0, 192, 167, 1, 0, 0, 0, 192, 183, 1, 0, 0, 0, 192, 190, 1, 0, 0, 0, 193, 202, 1, 0, 0, 0, 194, 195, 10, 5, 0, 0, 195, 196, 5, 31, 0, 0, 196, 201, 3, 10, 5, 6, 197, 198, 10, 4, 0, 0, 198, 199, 5, 48, 0, 0, 199, 201, 3, 10, 5, 5, 200, 194, 1, 0, 0, 0, 200, 197, 1, 0, 0, 0, 201, 204, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 202, 203, 1, 0, 0, 0, 203, 11, 1, 0, 0, 0, 204, 202, 1, 0, 0, 0, 205, 207, 3, 16, 8, 0, 206, 208, 5, 45, 0, 0, 207, 206, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 209, 1, 0, 0, 0, 209, 210, 5, 43, 0, 0, 210, 211, 3, 98, 49, 0, 211, 220, 1, 0, 0, 0, 212, 214, 3, 16, 8, 0, 213, 215, 5, 45, 0, 0, 214, 213, 1, 0, 0, 0, 214, 215, 1, 0, 0, 0, 215, 216, 1, 0, 0, 0, 216, 217, 5, 50, 0, 0, 217, 218, 3, 98, 49, 0, 218, 220, 1, 0, 0, 0, 219, 205, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 220, 13, 1, 0, 0, 0, 221, 222, 3, 16, 8, 0, 222, 223, 5, 20, 0, 0, 223, 224, 3, 98, 49, 0, 224, 15, 1, 0, 0, 0, 225, 231, 3, 18, 9, 0, 226, 227, 3, 18, 9, 0, 227, 228, 3, 100, 50, 0, 228, 229, 3, 18, 9, 0, 229, 231, 1, 0, 0, 0, 230, 225, 1, 0, 0, 0, 230, 226, 1, 0, 0, 0, 231, 17, 1, 0, 0, 0, 232, 233, 6, 9, -1, 0, 233, 237, 3, 20, 10, 0, 234, 235, 7, 0, 0, 0, 235, 237, 3, 18, 9, 3, 236, 232, 1, 0, 0, 0, 236, 234, 1, 0, 0, 0, 237, 246, 1, 0, 0, 0, 238, 239, 10, 2, 0, 0, 239, 240, 7, 1, 0, 0, 240, 245, 3, 18, 9, 3, 241, 242, 10, 1, 0, 0, 242, 243, 7, 0, 0, 0, 243, 245, 3, 18, 9, 2, 244, 238, 1, 0, 0, 0, 244, 241, 1, 0, 0, 0, 245, 248, 1, 0, 0, 0, 246, 244, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 247, 19, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 249, 250, 6, 10, -1, 0, 250, 258, 3, 62, 31, 0, 251, 258, 3, 52, 26, 0, 252, 258, 3, 22, 11, 0, 253, 254, 5, 44, 0, 0, 254, 255, 3, 10, 5, 0, 255, 256, 5, 51, 0, 0, 256, 258, 1, 0, 0, 0, 257, 249, 1, 0, 0, 0, 257, 251, 1, 0, 0, 0, 257, 252, 1, 0, 0, 0, 257, 253, 1, 0, 0, 0, 258, 264, 1, 0, 0, 0, 259, 260, 10, 1, 0, 0, 260, 261, 5, 34, 0, 0, 261, 263, 3, 24, 12, 0, 262, 259, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 21, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 268, 3, 58, 29, 0, 268, 278, 5, 44, 0, 0, 269, 279, 5, 62, 0, 0, 270, 275, 3, 10, 5, 0, 271, 272, 5, 35, 0, 0, 272, 274, 3, 10, 5, 0, 273, 271, 1, 0, 0, 0, 274, 277, 1, 0, 0, 0, 275, 273, 1, 0, 0, 0, 275, 276, 1, 0, 0, 0, 276, 279, 1, 0, 0, 0, 277, 275, 1, 0, 0, 0, 278, 269, 1, 0, 0, 0, 278, 270, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 281, 5, 51, 0, 0, 281, 23, 1, 0, 0, 0, 282, 283, 3, 58, 29, 0, 283, 25, 1, 0, 0, 0, 284, 285, 5, 13, 0, 0, 285, 286, 3, 28, 14, 0, 286, 27, 1, 0, 0, 0, 287, 292, 3, 30, 15, 0, 288, 289, 5, 35, 0, 0, 289, 291, 3, 30, 15, 0, 290, 288, 1, 0, 0, 0, 291, 294, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 29, 1, 0, 0, 0, 294, 292, 1, 0, 0, 0, 295, 301, 3, 10, 5, 0, 296, 297, 3, 52, 26, 0, 297, 298, 5, 33, 0, 0, 298, 299, 3, 10, 5, 0, 299, 301, 1, 0, 0, 0, 300, 295, 1, 0, 0, 0, 300, 296, 1, 0, 0, 0, 301, 31, 1, 0, 0, 0, 302, 303, 5, 6, 0, 0, 303, 308, 3, 34, 17, 0, 304, 305, 5, 35, 0, 0, 305, 307, 3, 34, 17, 0, 306, 304, 1, 0, 0, 0, 307, 310, 1, 0, 0, 0, 308, 306, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 312, 1, 0, 0, 0, 310, 308, 1, 0, 0, 0, 311, 313, 3, 40, 20, 0, 312, 311, 1, 0, 0, 0, 312, 313, 1, 0, 0, 0, 313, 33, 1, 0, 0, 0, 314, 315, 3, 36, 18, 0, 315, 316, 5, 109, 0, 0, 316, 317, 3, 38, 19, 0, 317, 320, 1, 0, 0, 0, 318, 320, 3, 38, 19, 0, 319, 314, 1, 0, 0, 0, 319, 318, 1, 0, 0, 0, 320, 35, 1, 0, 0, 0, 321, 322, 5, 77, 0, 0, 322, 37, 1, 0, 0, 0, 323, 324, 7, 2, 0, 0, 324, 39, 1, 0, 0, 0, 325, 328, 3, 42, 21, 0, 326, 328, 3, 44, 22, 0, 327, 325, 1, 0, 0, 0, 327, 326, 1, 0, 0, 0, 328, 41, 1, 0, 0, 0, 329, 330, 5, 76, 0, 0, 330, 335, 5, 77, 0, 0, 331, 332, 5, 35, 0, 0, 332, 334, 5, 77, 0, 0, 333, 331, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 333, 1, 0, 0, 0, 335, 336, 1, 0, 0, 0, 336, 43, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 339, 5, 66, 0, 0, 339, 340, 3, 42, 21, 0, 340, 341, 5, 67, 0, 0, 341, 45, 1, 0, 0, 0, 342, 343, 5, 21, 0, 0, 343, 348, 3, 34, 17, 0, 344, 345, 5, 35, 0, 0, 345, 347, 3, 34, 17, 0, 346, 344, 1, 0, 0, 0, 347, 350, 1, 0, 0, 0, 348, 346, 1, 0, 0, 0, 348, 349, 1, 0, 0, 0, 349, 352, 1, 0, 0, 0, 350, 348, 1, 0, 0, 0, 351, 353, 3, 28, 14, 0, 352, 351, 1, 0, 0, 0, 352, 353, 1, 0, 0, 0, 353, 356, 1, 0, 0, 0, 354, 355, 5, 30, 0, 0, 355, 357, 3, 28, 14, 0, 356, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 47, 1, 0, 0, 0, 358, 359, 5, 4, 0, 0, 359, 360, 3, 28, 14, 0, 360, 49, 1, 0, 0, 0, 361, 363, 5, 16, 0, 0, 362, 364, 3, 28, 14, 0, 363, 362, 1, 0, 0, 0, 363, 364, 1, 0, 0, 0, 364, 367, 1, 0, 0, 0, 365, 366, 5, 30, 0, 0, 366, 368, 3, 28, 14, 0, 367, 365, 1, 0, 0, 0, 367, 368, 1, 0, 0, 0, 368, 51, 1, 0, 0, 0, 369, 374, 3, 58, 29, 0, 370, 371, 5, 37, 0, 0, 371, 373, 3, 58, 29, 0, 372, 370, 1, 0, 0, 0, 373, 376, 1, 0, 0, 0, 374, 372, 1, 0, 0, 0, 374, 375, 1, 0, 0, 0, 375, 53, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 377, 382, 3, 60, 30, 0, 378, 379, 5, 37, 0, 0, 379, 381, 3, 60, 30, 0, 380, 378, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 55, 1, 0, 0, 0, 384, 382, 1, 0, 0, 0, 385, 390, 3, 54, 27, 0, 386, 387, 5, 35, 0, 0, 387, 389, 3, 54, 27, 0, 388, 386, 1, 0, 0, 0, 389, 392, 1, 0, 0, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 57, 1, 0, 0, 0, 392, 390, 1, 0, 0, 0, 393, 394, 7, 3, 0, 0, 394, 59, 1, 0, 0, 0, 395, 396, 5, 81, 0, 0, 396, 61, 1, 0, 0, 0, 397, 440, 5, 46, 0, 0, 398, 399, 3, 96, 48, 0, 399, 400, 5, 68, 0, 0, 400, 440, 1, 0, 0, 0, 401, 440, 3, 94, 47, 0, 402, 440, 3, 96, 48, 0, 403, 440, 3, 90, 45, 0, 404, 440, 3, 64, 32, 0, 405, 440, 3, 98, 49, 0, 406, 407, 5, 66, 0, 0, 407, 412, 3, 92, 46, 0, 408, 409, 5, 35, 0, 0, 409, 411, 3, 92, 46, 0, 410, 408, 1, 0, 0, 0, 411, 414, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 412, 413, 1, 0, 0, 0, 413, 415, 1, 0, 0, 0, 414, 412, 1, 0, 0, 0, 415, 416, 5, 67, 0, 0, 416, 440, 1, 0, 0, 0, 417, 418, 5, 66, 0, 0, 418, 423, 3, 90, 45, 0, 419, 420, 5, 35, 0, 0, 420, 422, 3, 90, 45, 0, 421, 419, 1, 0, 0, 0, 422, 425, 1, 0, 0, 0, 423, 421, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 426, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 5, 67, 0, 0, 427, 440, 1, 0, 0, 0, 428, 429, 5, 66, 0, 0, 429, 434, 3, 98, 49, 0, 430, 431, 5, 35, 0, 0, 431, 433, 3, 98, 49, 0, 432, 430, 1, 0, 0, 0, 433, 436, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 437, 1, 0, 0, 0, 436, 434, 1, 0, 0, 0, 437, 438, 5, 67, 0, 0, 438, 440, 1, 0, 0, 0, 439, 397, 1, 0, 0, 0, 439, 398, 1, 0, 0, 0, 439, 401, 1, 0, 0, 0, 439, 402, 1, 0, 0, 0, 439, 403, 1, 0, 0, 0, 439, 404, 1, 0, 0, 0, 439, 405, 1, 0, 0, 0, 439, 406, 1, 0, 0, 0, 439, 417, 1, 0, 0, 0, 439, 428, 1, 0, 0, 0, 440, 63, 1, 0, 0, 0, 441, 444, 5, 49, 0, 0, 442, 444, 5, 65, 0, 0, 443, 441, 1, 0, 0, 0, 443, 442, 1, 0, 0, 0, 444, 65, 1, 0, 0, 0, 445, 446, 5, 9, 0, 0, 446, 447, 5, 28, 0, 0, 447, 67, 1, 0, 0, 0, 448, 449, 5, 15, 0, 0, 449, 454, 3, 70, 35, 0, 450, 451, 5, 35, 0, 0, 451, 453, 3, 70, 35, 0, 452, 450, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 69, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 457, 459, 3, 10, 5, 0, 458, 460, 7, 4, 0, 0, 459, 458, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 463, 1, 0, 0, 0, 461, 462, 5, 47, 0, 0, 462, 464, 7, 5, 0, 0, 463, 461, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 71, 1, 0, 0, 0, 465, 466, 5, 8, 0, 0, 466, 467, 3, 56, 28, 0, 467, 73, 1, 0, 0, 0, 468, 469, 5, 2, 0, 0, 469, 470, 3, 56, 28, 0, 470, 75, 1, 0, 0, 0, 471, 472, 5, 12, 0, 0, 472, 477, 3, 78, 39, 0, 473, 474, 5, 35, 0, 0, 474, 476, 3, 78, 39, 0, 475, 473, 1, 0, 0, 0, 476, 479, 1, 0, 0, 0, 477, 475, 1, 0, 0, 0, 477, 478, 1, 0, 0, 0, 478, 77, 1, 0, 0, 0, 479, 477, 1, 0, 0, 0, 480, 481, 3, 54, 27, 0, 481, 482, 5, 85, 0, 0, 482, 483, 3, 54, 27, 0, 483, 79, 1, 0, 0, 0, 484, 485, 5, 1, 0, 0, 485, 486, 3, 20, 10, 0, 486, 488, 3, 98, 49, 0, 487, 489, 3, 86, 43, 0, 488, 487, 1, 0, 0, 0, 488, 489, 1, 0, 0, 0, 489, 81, 1, 0, 0, 0, 490, 491, 5, 7, 0, 0, 491, 492, 3, 20, 10, 0, 492, 493, 3, 98, 49, 0, 493, 83, 1, 0, 0, 0, 494, 495, 5, 11, 0, 0, 495, 496, 3, 52, 26, 0, 496, 85, 1, 0, 0, 0, 497, 502, 3, 88, 44, 0, 498, 499, 5, 35, 0, 0, 499, 501, 3, 88, 44, 0, 500, 498, 1, 0, 0, 0, 501, 504, 1, 0, 0, 0, 502, 500, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 87, 1, 0, 0, 0, 504, 502, 1, 0, 0, 0, 505, 506, 3, 58, 29, 0, 506, 507, 5, 33, 0, 0, 507, 508, 3, 62, 31, 0, 508, 89, 1, 0, 0, 0, 509, 510, 7, 6, 0, 0, 510, 91, 1, 0, 0, 0, 511, 514, 3, 94, 47, 0, 512, 514, 3, 96, 48, 0, 513, 511, 1, 0, 0, 0, 513, 512, 1, 0, 0, 0, 514, 93, 1, 0, 0, 0, 515, 517, 7, 0, 0, 0, 516, 515, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 519, 5, 29, 0, 0, 519, 95, 1, 0, 0, 0, 520, 522, 7, 0, 0, 0, 521, 520, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 1, 0, 0, 0, 523, 524, 5, 28, 0, 0, 524, 97, 1, 0, 0, 0, 525, 526, 5, 27, 0, 0, 526, 99, 1, 0, 0, 0, 527, 528, 7, 7, 0, 0, 528, 101, 1, 0, 0, 0, 529, 530, 5, 5, 0, 0, 530, 531, 3, 104, 52, 0, 531, 103, 1, 0, 0, 0, 532, 533, 5, 66, 0, 0, 533, 534, 3, 2, 1, 0, 534, 535, 5, 67, 0, 0, 535, 105, 1, 0, 0, 0, 536, 537, 5, 14, 0, 0, 537, 538, 5, 101, 0, 0, 538, 107, 1, 0, 0, 0, 539, 540, 5, 10, 0, 0, 540, 541, 5, 105, 0, 0, 541, 109, 1, 0, 0, 0, 542, 543, 5, 3, 0, 0, 543, 546, 5, 91, 0, 0, 544, 545, 5, 89, 0, 0, 545, 547, 3, 54, 27, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 557, 1, 0, 0, 0, 548, 549, 5, 90, 0, 0, 549, 554, 3, 112, 56, 0, 550, 551, 5, 35, 0, 0, 551, 553, 3, 112, 56, 0, 552, 550, 1, 0, 0, 0, 553, 556, 1, 0, 0, 0, 554, 552, 1, 0, 0, 0, 554, 555, 1, 0, 0, 0, 555, 558, 1, 0, 0, 0, 556, 554, 1, 0, 0, 0, 557, 548, 1, 0, 0, 0, 557, 558, 1, 0, 0, 0, 558, 111, 1, 0, 0, 0, 559, 560, 3, 54, 27, 0, 560, 561, 5, 33, 0, 0, 561, 563, 1, 0, 0, 0, 562, 559, 1, 0, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 1, 0, 0, 0, 564, 565, 3, 54, 27, 0, 565, 113, 1, 0, 0, 0, 566, 567, 5, 19, 0, 0, 567, 568, 3, 34, 17, 0, 568, 569, 5, 89, 0, 0, 569, 570, 3, 56, 28, 0, 570, 115, 1, 0, 0, 0, 571, 572, 5, 18, 0, 0, 572, 575, 3, 28, 14, 0, 573, 574, 5, 30, 0, 0, 574, 576, 3, 28, 14, 0, 575, 573, 1, 0, 0, 0, 575, 576, 1, 0, 0, 0, 576, 117, 1, 0, 0, 0, 54, 129, 139, 157, 169, 178, 186, 192, 200, 202, 207, 214, 219, 230, 236, 244, 246, 257, 264, 275, 278, 292, 300, 308, 312, 319, 327, 335, 348, 352, 356, 363, 367, 374, 382, 390, 412, 423, 434, 439, 443, 454, 459, 463, 477, 488, 502, 513, 516, 521, 546, 554, 557, 562, 575] \ No newline at end of file +[4, 1, 120, 580, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 128, 8, 1, 10, 1, 12, 1, 131, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 139, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 157, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 169, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 176, 8, 5, 10, 5, 12, 5, 179, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 186, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 192, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 200, 8, 5, 10, 5, 12, 5, 203, 9, 5, 1, 6, 1, 6, 3, 6, 207, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 214, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 219, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 230, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 236, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 257, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 262, 8, 10, 10, 10, 12, 10, 265, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 273, 8, 11, 10, 11, 12, 11, 276, 9, 11, 3, 11, 278, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 5, 14, 290, 8, 14, 10, 14, 12, 14, 293, 9, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 300, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 306, 8, 16, 10, 16, 12, 16, 309, 9, 16, 1, 16, 3, 16, 312, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 319, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 327, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 333, 8, 21, 10, 21, 12, 21, 336, 9, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 346, 8, 23, 10, 23, 12, 23, 349, 9, 23, 1, 23, 3, 23, 352, 8, 23, 1, 23, 1, 23, 3, 23, 356, 8, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 3, 25, 363, 8, 25, 1, 25, 1, 25, 3, 25, 367, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 372, 8, 26, 10, 26, 12, 26, 375, 9, 26, 1, 27, 1, 27, 1, 27, 5, 27, 380, 8, 27, 10, 27, 12, 27, 383, 9, 27, 1, 28, 1, 28, 1, 28, 5, 28, 388, 8, 28, 10, 28, 12, 28, 391, 9, 28, 1, 29, 1, 29, 1, 30, 1, 30, 3, 30, 397, 8, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 412, 8, 31, 10, 31, 12, 31, 415, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 423, 8, 31, 10, 31, 12, 31, 426, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 434, 8, 31, 10, 31, 12, 31, 437, 9, 31, 1, 31, 1, 31, 3, 31, 441, 8, 31, 1, 32, 1, 32, 3, 32, 445, 8, 32, 1, 33, 1, 33, 3, 33, 449, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 458, 8, 35, 10, 35, 12, 35, 461, 9, 35, 1, 36, 1, 36, 3, 36, 465, 8, 36, 1, 36, 1, 36, 3, 36, 469, 8, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 481, 8, 39, 10, 39, 12, 39, 484, 9, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 494, 8, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 5, 44, 506, 8, 44, 10, 44, 12, 44, 509, 9, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 3, 47, 519, 8, 47, 1, 48, 3, 48, 522, 8, 48, 1, 48, 1, 48, 1, 49, 3, 49, 527, 8, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 3, 55, 549, 8, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 555, 8, 55, 10, 55, 12, 55, 558, 9, 55, 3, 55, 560, 8, 55, 1, 56, 1, 56, 1, 56, 3, 56, 565, 8, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 578, 8, 58, 1, 58, 0, 4, 2, 10, 18, 20, 59, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 0, 8, 1, 0, 59, 60, 1, 0, 61, 63, 2, 0, 26, 26, 76, 76, 1, 0, 67, 68, 2, 0, 31, 31, 35, 35, 2, 0, 38, 38, 41, 41, 2, 0, 37, 37, 51, 51, 2, 0, 52, 52, 54, 58, 606, 0, 118, 1, 0, 0, 0, 2, 121, 1, 0, 0, 0, 4, 138, 1, 0, 0, 0, 6, 156, 1, 0, 0, 0, 8, 158, 1, 0, 0, 0, 10, 191, 1, 0, 0, 0, 12, 218, 1, 0, 0, 0, 14, 220, 1, 0, 0, 0, 16, 229, 1, 0, 0, 0, 18, 235, 1, 0, 0, 0, 20, 256, 1, 0, 0, 0, 22, 266, 1, 0, 0, 0, 24, 281, 1, 0, 0, 0, 26, 283, 1, 0, 0, 0, 28, 286, 1, 0, 0, 0, 30, 299, 1, 0, 0, 0, 32, 301, 1, 0, 0, 0, 34, 318, 1, 0, 0, 0, 36, 320, 1, 0, 0, 0, 38, 322, 1, 0, 0, 0, 40, 326, 1, 0, 0, 0, 42, 328, 1, 0, 0, 0, 44, 337, 1, 0, 0, 0, 46, 341, 1, 0, 0, 0, 48, 357, 1, 0, 0, 0, 50, 360, 1, 0, 0, 0, 52, 368, 1, 0, 0, 0, 54, 376, 1, 0, 0, 0, 56, 384, 1, 0, 0, 0, 58, 392, 1, 0, 0, 0, 60, 396, 1, 0, 0, 0, 62, 440, 1, 0, 0, 0, 64, 444, 1, 0, 0, 0, 66, 448, 1, 0, 0, 0, 68, 450, 1, 0, 0, 0, 70, 453, 1, 0, 0, 0, 72, 462, 1, 0, 0, 0, 74, 470, 1, 0, 0, 0, 76, 473, 1, 0, 0, 0, 78, 476, 1, 0, 0, 0, 80, 485, 1, 0, 0, 0, 82, 489, 1, 0, 0, 0, 84, 495, 1, 0, 0, 0, 86, 499, 1, 0, 0, 0, 88, 502, 1, 0, 0, 0, 90, 510, 1, 0, 0, 0, 92, 514, 1, 0, 0, 0, 94, 518, 1, 0, 0, 0, 96, 521, 1, 0, 0, 0, 98, 526, 1, 0, 0, 0, 100, 530, 1, 0, 0, 0, 102, 532, 1, 0, 0, 0, 104, 534, 1, 0, 0, 0, 106, 537, 1, 0, 0, 0, 108, 541, 1, 0, 0, 0, 110, 544, 1, 0, 0, 0, 112, 564, 1, 0, 0, 0, 114, 568, 1, 0, 0, 0, 116, 573, 1, 0, 0, 0, 118, 119, 3, 2, 1, 0, 119, 120, 5, 0, 0, 1, 120, 1, 1, 0, 0, 0, 121, 122, 6, 1, -1, 0, 122, 123, 3, 4, 2, 0, 123, 129, 1, 0, 0, 0, 124, 125, 10, 1, 0, 0, 125, 126, 5, 25, 0, 0, 126, 128, 3, 6, 3, 0, 127, 124, 1, 0, 0, 0, 128, 131, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 3, 1, 0, 0, 0, 131, 129, 1, 0, 0, 0, 132, 139, 3, 104, 52, 0, 133, 139, 3, 32, 16, 0, 134, 139, 3, 26, 13, 0, 135, 139, 3, 108, 54, 0, 136, 137, 4, 2, 1, 0, 137, 139, 3, 46, 23, 0, 138, 132, 1, 0, 0, 0, 138, 133, 1, 0, 0, 0, 138, 134, 1, 0, 0, 0, 138, 135, 1, 0, 0, 0, 138, 136, 1, 0, 0, 0, 139, 5, 1, 0, 0, 0, 140, 157, 3, 48, 24, 0, 141, 157, 3, 8, 4, 0, 142, 157, 3, 74, 37, 0, 143, 157, 3, 68, 34, 0, 144, 157, 3, 50, 25, 0, 145, 157, 3, 70, 35, 0, 146, 157, 3, 76, 38, 0, 147, 157, 3, 78, 39, 0, 148, 157, 3, 82, 41, 0, 149, 157, 3, 84, 42, 0, 150, 157, 3, 110, 55, 0, 151, 157, 3, 86, 43, 0, 152, 153, 4, 3, 2, 0, 153, 157, 3, 116, 58, 0, 154, 155, 4, 3, 3, 0, 155, 157, 3, 114, 57, 0, 156, 140, 1, 0, 0, 0, 156, 141, 1, 0, 0, 0, 156, 142, 1, 0, 0, 0, 156, 143, 1, 0, 0, 0, 156, 144, 1, 0, 0, 0, 156, 145, 1, 0, 0, 0, 156, 146, 1, 0, 0, 0, 156, 147, 1, 0, 0, 0, 156, 148, 1, 0, 0, 0, 156, 149, 1, 0, 0, 0, 156, 150, 1, 0, 0, 0, 156, 151, 1, 0, 0, 0, 156, 152, 1, 0, 0, 0, 156, 154, 1, 0, 0, 0, 157, 7, 1, 0, 0, 0, 158, 159, 5, 16, 0, 0, 159, 160, 3, 10, 5, 0, 160, 9, 1, 0, 0, 0, 161, 162, 6, 5, -1, 0, 162, 163, 5, 44, 0, 0, 163, 192, 3, 10, 5, 8, 164, 192, 3, 16, 8, 0, 165, 192, 3, 12, 6, 0, 166, 168, 3, 16, 8, 0, 167, 169, 5, 44, 0, 0, 168, 167, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 171, 5, 39, 0, 0, 171, 172, 5, 43, 0, 0, 172, 177, 3, 16, 8, 0, 173, 174, 5, 34, 0, 0, 174, 176, 3, 16, 8, 0, 175, 173, 1, 0, 0, 0, 176, 179, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 177, 178, 1, 0, 0, 0, 178, 180, 1, 0, 0, 0, 179, 177, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 192, 1, 0, 0, 0, 182, 183, 3, 16, 8, 0, 183, 185, 5, 40, 0, 0, 184, 186, 5, 44, 0, 0, 185, 184, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 45, 0, 0, 188, 192, 1, 0, 0, 0, 189, 190, 4, 5, 4, 0, 190, 192, 3, 14, 7, 0, 191, 161, 1, 0, 0, 0, 191, 164, 1, 0, 0, 0, 191, 165, 1, 0, 0, 0, 191, 166, 1, 0, 0, 0, 191, 182, 1, 0, 0, 0, 191, 189, 1, 0, 0, 0, 192, 201, 1, 0, 0, 0, 193, 194, 10, 5, 0, 0, 194, 195, 5, 30, 0, 0, 195, 200, 3, 10, 5, 6, 196, 197, 10, 4, 0, 0, 197, 198, 5, 47, 0, 0, 198, 200, 3, 10, 5, 5, 199, 193, 1, 0, 0, 0, 199, 196, 1, 0, 0, 0, 200, 203, 1, 0, 0, 0, 201, 199, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 11, 1, 0, 0, 0, 203, 201, 1, 0, 0, 0, 204, 206, 3, 16, 8, 0, 205, 207, 5, 44, 0, 0, 206, 205, 1, 0, 0, 0, 206, 207, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 209, 5, 42, 0, 0, 209, 210, 3, 100, 50, 0, 210, 219, 1, 0, 0, 0, 211, 213, 3, 16, 8, 0, 212, 214, 5, 44, 0, 0, 213, 212, 1, 0, 0, 0, 213, 214, 1, 0, 0, 0, 214, 215, 1, 0, 0, 0, 215, 216, 5, 49, 0, 0, 216, 217, 3, 100, 50, 0, 217, 219, 1, 0, 0, 0, 218, 204, 1, 0, 0, 0, 218, 211, 1, 0, 0, 0, 219, 13, 1, 0, 0, 0, 220, 221, 3, 16, 8, 0, 221, 222, 5, 19, 0, 0, 222, 223, 3, 100, 50, 0, 223, 15, 1, 0, 0, 0, 224, 230, 3, 18, 9, 0, 225, 226, 3, 18, 9, 0, 226, 227, 3, 102, 51, 0, 227, 228, 3, 18, 9, 0, 228, 230, 1, 0, 0, 0, 229, 224, 1, 0, 0, 0, 229, 225, 1, 0, 0, 0, 230, 17, 1, 0, 0, 0, 231, 232, 6, 9, -1, 0, 232, 236, 3, 20, 10, 0, 233, 234, 7, 0, 0, 0, 234, 236, 3, 18, 9, 3, 235, 231, 1, 0, 0, 0, 235, 233, 1, 0, 0, 0, 236, 245, 1, 0, 0, 0, 237, 238, 10, 2, 0, 0, 238, 239, 7, 1, 0, 0, 239, 244, 3, 18, 9, 3, 240, 241, 10, 1, 0, 0, 241, 242, 7, 0, 0, 0, 242, 244, 3, 18, 9, 2, 243, 237, 1, 0, 0, 0, 243, 240, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 249, 6, 10, -1, 0, 249, 257, 3, 62, 31, 0, 250, 257, 3, 52, 26, 0, 251, 257, 3, 22, 11, 0, 252, 253, 5, 43, 0, 0, 253, 254, 3, 10, 5, 0, 254, 255, 5, 50, 0, 0, 255, 257, 1, 0, 0, 0, 256, 248, 1, 0, 0, 0, 256, 250, 1, 0, 0, 0, 256, 251, 1, 0, 0, 0, 256, 252, 1, 0, 0, 0, 257, 263, 1, 0, 0, 0, 258, 259, 10, 1, 0, 0, 259, 260, 5, 33, 0, 0, 260, 262, 3, 24, 12, 0, 261, 258, 1, 0, 0, 0, 262, 265, 1, 0, 0, 0, 263, 261, 1, 0, 0, 0, 263, 264, 1, 0, 0, 0, 264, 21, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 266, 267, 3, 66, 33, 0, 267, 277, 5, 43, 0, 0, 268, 278, 5, 61, 0, 0, 269, 274, 3, 10, 5, 0, 270, 271, 5, 34, 0, 0, 271, 273, 3, 10, 5, 0, 272, 270, 1, 0, 0, 0, 273, 276, 1, 0, 0, 0, 274, 272, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 278, 1, 0, 0, 0, 276, 274, 1, 0, 0, 0, 277, 268, 1, 0, 0, 0, 277, 269, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 280, 5, 50, 0, 0, 280, 23, 1, 0, 0, 0, 281, 282, 3, 58, 29, 0, 282, 25, 1, 0, 0, 0, 283, 284, 5, 12, 0, 0, 284, 285, 3, 28, 14, 0, 285, 27, 1, 0, 0, 0, 286, 291, 3, 30, 15, 0, 287, 288, 5, 34, 0, 0, 288, 290, 3, 30, 15, 0, 289, 287, 1, 0, 0, 0, 290, 293, 1, 0, 0, 0, 291, 289, 1, 0, 0, 0, 291, 292, 1, 0, 0, 0, 292, 29, 1, 0, 0, 0, 293, 291, 1, 0, 0, 0, 294, 300, 3, 10, 5, 0, 295, 296, 3, 52, 26, 0, 296, 297, 5, 32, 0, 0, 297, 298, 3, 10, 5, 0, 298, 300, 1, 0, 0, 0, 299, 294, 1, 0, 0, 0, 299, 295, 1, 0, 0, 0, 300, 31, 1, 0, 0, 0, 301, 302, 5, 6, 0, 0, 302, 307, 3, 34, 17, 0, 303, 304, 5, 34, 0, 0, 304, 306, 3, 34, 17, 0, 305, 303, 1, 0, 0, 0, 306, 309, 1, 0, 0, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 311, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 310, 312, 3, 40, 20, 0, 311, 310, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 33, 1, 0, 0, 0, 313, 314, 3, 36, 18, 0, 314, 315, 5, 104, 0, 0, 315, 316, 3, 38, 19, 0, 316, 319, 1, 0, 0, 0, 317, 319, 3, 38, 19, 0, 318, 313, 1, 0, 0, 0, 318, 317, 1, 0, 0, 0, 319, 35, 1, 0, 0, 0, 320, 321, 5, 76, 0, 0, 321, 37, 1, 0, 0, 0, 322, 323, 7, 2, 0, 0, 323, 39, 1, 0, 0, 0, 324, 327, 3, 42, 21, 0, 325, 327, 3, 44, 22, 0, 326, 324, 1, 0, 0, 0, 326, 325, 1, 0, 0, 0, 327, 41, 1, 0, 0, 0, 328, 329, 5, 75, 0, 0, 329, 334, 5, 76, 0, 0, 330, 331, 5, 34, 0, 0, 331, 333, 5, 76, 0, 0, 332, 330, 1, 0, 0, 0, 333, 336, 1, 0, 0, 0, 334, 332, 1, 0, 0, 0, 334, 335, 1, 0, 0, 0, 335, 43, 1, 0, 0, 0, 336, 334, 1, 0, 0, 0, 337, 338, 5, 65, 0, 0, 338, 339, 3, 42, 21, 0, 339, 340, 5, 66, 0, 0, 340, 45, 1, 0, 0, 0, 341, 342, 5, 20, 0, 0, 342, 347, 3, 34, 17, 0, 343, 344, 5, 34, 0, 0, 344, 346, 3, 34, 17, 0, 345, 343, 1, 0, 0, 0, 346, 349, 1, 0, 0, 0, 347, 345, 1, 0, 0, 0, 347, 348, 1, 0, 0, 0, 348, 351, 1, 0, 0, 0, 349, 347, 1, 0, 0, 0, 350, 352, 3, 28, 14, 0, 351, 350, 1, 0, 0, 0, 351, 352, 1, 0, 0, 0, 352, 355, 1, 0, 0, 0, 353, 354, 5, 29, 0, 0, 354, 356, 3, 28, 14, 0, 355, 353, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 47, 1, 0, 0, 0, 357, 358, 5, 4, 0, 0, 358, 359, 3, 28, 14, 0, 359, 49, 1, 0, 0, 0, 360, 362, 5, 15, 0, 0, 361, 363, 3, 28, 14, 0, 362, 361, 1, 0, 0, 0, 362, 363, 1, 0, 0, 0, 363, 366, 1, 0, 0, 0, 364, 365, 5, 29, 0, 0, 365, 367, 3, 28, 14, 0, 366, 364, 1, 0, 0, 0, 366, 367, 1, 0, 0, 0, 367, 51, 1, 0, 0, 0, 368, 373, 3, 66, 33, 0, 369, 370, 5, 36, 0, 0, 370, 372, 3, 66, 33, 0, 371, 369, 1, 0, 0, 0, 372, 375, 1, 0, 0, 0, 373, 371, 1, 0, 0, 0, 373, 374, 1, 0, 0, 0, 374, 53, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 376, 381, 3, 60, 30, 0, 377, 378, 5, 36, 0, 0, 378, 380, 3, 60, 30, 0, 379, 377, 1, 0, 0, 0, 380, 383, 1, 0, 0, 0, 381, 379, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 55, 1, 0, 0, 0, 383, 381, 1, 0, 0, 0, 384, 389, 3, 54, 27, 0, 385, 386, 5, 34, 0, 0, 386, 388, 3, 54, 27, 0, 387, 385, 1, 0, 0, 0, 388, 391, 1, 0, 0, 0, 389, 387, 1, 0, 0, 0, 389, 390, 1, 0, 0, 0, 390, 57, 1, 0, 0, 0, 391, 389, 1, 0, 0, 0, 392, 393, 7, 3, 0, 0, 393, 59, 1, 0, 0, 0, 394, 397, 5, 80, 0, 0, 395, 397, 3, 64, 32, 0, 396, 394, 1, 0, 0, 0, 396, 395, 1, 0, 0, 0, 397, 61, 1, 0, 0, 0, 398, 441, 5, 45, 0, 0, 399, 400, 3, 98, 49, 0, 400, 401, 5, 67, 0, 0, 401, 441, 1, 0, 0, 0, 402, 441, 3, 96, 48, 0, 403, 441, 3, 98, 49, 0, 404, 441, 3, 92, 46, 0, 405, 441, 3, 64, 32, 0, 406, 441, 3, 100, 50, 0, 407, 408, 5, 65, 0, 0, 408, 413, 3, 94, 47, 0, 409, 410, 5, 34, 0, 0, 410, 412, 3, 94, 47, 0, 411, 409, 1, 0, 0, 0, 412, 415, 1, 0, 0, 0, 413, 411, 1, 0, 0, 0, 413, 414, 1, 0, 0, 0, 414, 416, 1, 0, 0, 0, 415, 413, 1, 0, 0, 0, 416, 417, 5, 66, 0, 0, 417, 441, 1, 0, 0, 0, 418, 419, 5, 65, 0, 0, 419, 424, 3, 92, 46, 0, 420, 421, 5, 34, 0, 0, 421, 423, 3, 92, 46, 0, 422, 420, 1, 0, 0, 0, 423, 426, 1, 0, 0, 0, 424, 422, 1, 0, 0, 0, 424, 425, 1, 0, 0, 0, 425, 427, 1, 0, 0, 0, 426, 424, 1, 0, 0, 0, 427, 428, 5, 66, 0, 0, 428, 441, 1, 0, 0, 0, 429, 430, 5, 65, 0, 0, 430, 435, 3, 100, 50, 0, 431, 432, 5, 34, 0, 0, 432, 434, 3, 100, 50, 0, 433, 431, 1, 0, 0, 0, 434, 437, 1, 0, 0, 0, 435, 433, 1, 0, 0, 0, 435, 436, 1, 0, 0, 0, 436, 438, 1, 0, 0, 0, 437, 435, 1, 0, 0, 0, 438, 439, 5, 66, 0, 0, 439, 441, 1, 0, 0, 0, 440, 398, 1, 0, 0, 0, 440, 399, 1, 0, 0, 0, 440, 402, 1, 0, 0, 0, 440, 403, 1, 0, 0, 0, 440, 404, 1, 0, 0, 0, 440, 405, 1, 0, 0, 0, 440, 406, 1, 0, 0, 0, 440, 407, 1, 0, 0, 0, 440, 418, 1, 0, 0, 0, 440, 429, 1, 0, 0, 0, 441, 63, 1, 0, 0, 0, 442, 445, 5, 48, 0, 0, 443, 445, 5, 64, 0, 0, 444, 442, 1, 0, 0, 0, 444, 443, 1, 0, 0, 0, 445, 65, 1, 0, 0, 0, 446, 449, 3, 58, 29, 0, 447, 449, 3, 64, 32, 0, 448, 446, 1, 0, 0, 0, 448, 447, 1, 0, 0, 0, 449, 67, 1, 0, 0, 0, 450, 451, 5, 9, 0, 0, 451, 452, 5, 27, 0, 0, 452, 69, 1, 0, 0, 0, 453, 454, 5, 14, 0, 0, 454, 459, 3, 72, 36, 0, 455, 456, 5, 34, 0, 0, 456, 458, 3, 72, 36, 0, 457, 455, 1, 0, 0, 0, 458, 461, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 71, 1, 0, 0, 0, 461, 459, 1, 0, 0, 0, 462, 464, 3, 10, 5, 0, 463, 465, 7, 4, 0, 0, 464, 463, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 468, 1, 0, 0, 0, 466, 467, 5, 46, 0, 0, 467, 469, 7, 5, 0, 0, 468, 466, 1, 0, 0, 0, 468, 469, 1, 0, 0, 0, 469, 73, 1, 0, 0, 0, 470, 471, 5, 8, 0, 0, 471, 472, 3, 56, 28, 0, 472, 75, 1, 0, 0, 0, 473, 474, 5, 2, 0, 0, 474, 475, 3, 56, 28, 0, 475, 77, 1, 0, 0, 0, 476, 477, 5, 11, 0, 0, 477, 482, 3, 80, 40, 0, 478, 479, 5, 34, 0, 0, 479, 481, 3, 80, 40, 0, 480, 478, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 480, 1, 0, 0, 0, 482, 483, 1, 0, 0, 0, 483, 79, 1, 0, 0, 0, 484, 482, 1, 0, 0, 0, 485, 486, 3, 54, 27, 0, 486, 487, 5, 84, 0, 0, 487, 488, 3, 54, 27, 0, 488, 81, 1, 0, 0, 0, 489, 490, 5, 1, 0, 0, 490, 491, 3, 20, 10, 0, 491, 493, 3, 100, 50, 0, 492, 494, 3, 88, 44, 0, 493, 492, 1, 0, 0, 0, 493, 494, 1, 0, 0, 0, 494, 83, 1, 0, 0, 0, 495, 496, 5, 7, 0, 0, 496, 497, 3, 20, 10, 0, 497, 498, 3, 100, 50, 0, 498, 85, 1, 0, 0, 0, 499, 500, 5, 10, 0, 0, 500, 501, 3, 52, 26, 0, 501, 87, 1, 0, 0, 0, 502, 507, 3, 90, 45, 0, 503, 504, 5, 34, 0, 0, 504, 506, 3, 90, 45, 0, 505, 503, 1, 0, 0, 0, 506, 509, 1, 0, 0, 0, 507, 505, 1, 0, 0, 0, 507, 508, 1, 0, 0, 0, 508, 89, 1, 0, 0, 0, 509, 507, 1, 0, 0, 0, 510, 511, 3, 58, 29, 0, 511, 512, 5, 32, 0, 0, 512, 513, 3, 62, 31, 0, 513, 91, 1, 0, 0, 0, 514, 515, 7, 6, 0, 0, 515, 93, 1, 0, 0, 0, 516, 519, 3, 96, 48, 0, 517, 519, 3, 98, 49, 0, 518, 516, 1, 0, 0, 0, 518, 517, 1, 0, 0, 0, 519, 95, 1, 0, 0, 0, 520, 522, 7, 0, 0, 0, 521, 520, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 1, 0, 0, 0, 523, 524, 5, 28, 0, 0, 524, 97, 1, 0, 0, 0, 525, 527, 7, 0, 0, 0, 526, 525, 1, 0, 0, 0, 526, 527, 1, 0, 0, 0, 527, 528, 1, 0, 0, 0, 528, 529, 5, 27, 0, 0, 529, 99, 1, 0, 0, 0, 530, 531, 5, 26, 0, 0, 531, 101, 1, 0, 0, 0, 532, 533, 7, 7, 0, 0, 533, 103, 1, 0, 0, 0, 534, 535, 5, 5, 0, 0, 535, 536, 3, 106, 53, 0, 536, 105, 1, 0, 0, 0, 537, 538, 5, 65, 0, 0, 538, 539, 3, 2, 1, 0, 539, 540, 5, 66, 0, 0, 540, 107, 1, 0, 0, 0, 541, 542, 5, 13, 0, 0, 542, 543, 5, 100, 0, 0, 543, 109, 1, 0, 0, 0, 544, 545, 5, 3, 0, 0, 545, 548, 5, 90, 0, 0, 546, 547, 5, 88, 0, 0, 547, 549, 3, 54, 27, 0, 548, 546, 1, 0, 0, 0, 548, 549, 1, 0, 0, 0, 549, 559, 1, 0, 0, 0, 550, 551, 5, 89, 0, 0, 551, 556, 3, 112, 56, 0, 552, 553, 5, 34, 0, 0, 553, 555, 3, 112, 56, 0, 554, 552, 1, 0, 0, 0, 555, 558, 1, 0, 0, 0, 556, 554, 1, 0, 0, 0, 556, 557, 1, 0, 0, 0, 557, 560, 1, 0, 0, 0, 558, 556, 1, 0, 0, 0, 559, 550, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 111, 1, 0, 0, 0, 561, 562, 3, 54, 27, 0, 562, 563, 5, 32, 0, 0, 563, 565, 1, 0, 0, 0, 564, 561, 1, 0, 0, 0, 564, 565, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 567, 3, 54, 27, 0, 567, 113, 1, 0, 0, 0, 568, 569, 5, 18, 0, 0, 569, 570, 3, 34, 17, 0, 570, 571, 5, 88, 0, 0, 571, 572, 3, 56, 28, 0, 572, 115, 1, 0, 0, 0, 573, 574, 5, 17, 0, 0, 574, 577, 3, 28, 14, 0, 575, 576, 5, 29, 0, 0, 576, 578, 3, 28, 14, 0, 577, 575, 1, 0, 0, 0, 577, 578, 1, 0, 0, 0, 578, 117, 1, 0, 0, 0, 56, 129, 138, 156, 168, 177, 185, 191, 199, 201, 206, 213, 218, 229, 235, 243, 245, 256, 263, 274, 277, 291, 299, 307, 311, 318, 326, 334, 347, 351, 355, 362, 366, 373, 381, 389, 396, 413, 424, 435, 440, 444, 448, 459, 464, 468, 482, 493, 507, 518, 521, 526, 548, 556, 559, 564, 577] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens index 747fbbc64cf5f..4fd37ab9900f2 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens @@ -7,122 +7,117 @@ FROM=6 GROK=7 KEEP=8 LIMIT=9 -META=10 -MV_EXPAND=11 -RENAME=12 -ROW=13 -SHOW=14 -SORT=15 -STATS=16 -WHERE=17 -DEV_INLINESTATS=18 -DEV_LOOKUP=19 -DEV_MATCH=20 -DEV_METRICS=21 -UNKNOWN_CMD=22 -LINE_COMMENT=23 -MULTILINE_COMMENT=24 -WS=25 -PIPE=26 -QUOTED_STRING=27 -INTEGER_LITERAL=28 -DECIMAL_LITERAL=29 -BY=30 -AND=31 -ASC=32 -ASSIGN=33 -CAST_OP=34 -COMMA=35 -DESC=36 -DOT=37 -FALSE=38 -FIRST=39 -IN=40 -IS=41 -LAST=42 -LIKE=43 -LP=44 -NOT=45 -NULL=46 -NULLS=47 -OR=48 -PARAM=49 -RLIKE=50 -RP=51 -TRUE=52 -EQ=53 -CIEQ=54 -NEQ=55 -LT=56 -LTE=57 -GT=58 -GTE=59 -PLUS=60 -MINUS=61 -ASTERISK=62 -SLASH=63 -PERCENT=64 -NAMED_OR_POSITIONAL_PARAM=65 -OPENING_BRACKET=66 -CLOSING_BRACKET=67 -UNQUOTED_IDENTIFIER=68 -QUOTED_IDENTIFIER=69 -EXPR_LINE_COMMENT=70 -EXPR_MULTILINE_COMMENT=71 -EXPR_WS=72 -EXPLAIN_WS=73 -EXPLAIN_LINE_COMMENT=74 -EXPLAIN_MULTILINE_COMMENT=75 -METADATA=76 -UNQUOTED_SOURCE=77 -FROM_LINE_COMMENT=78 -FROM_MULTILINE_COMMENT=79 -FROM_WS=80 -ID_PATTERN=81 -PROJECT_LINE_COMMENT=82 -PROJECT_MULTILINE_COMMENT=83 -PROJECT_WS=84 -AS=85 -RENAME_LINE_COMMENT=86 -RENAME_MULTILINE_COMMENT=87 -RENAME_WS=88 -ON=89 -WITH=90 -ENRICH_POLICY_NAME=91 -ENRICH_LINE_COMMENT=92 -ENRICH_MULTILINE_COMMENT=93 -ENRICH_WS=94 -ENRICH_FIELD_LINE_COMMENT=95 -ENRICH_FIELD_MULTILINE_COMMENT=96 -ENRICH_FIELD_WS=97 -MVEXPAND_LINE_COMMENT=98 -MVEXPAND_MULTILINE_COMMENT=99 -MVEXPAND_WS=100 -INFO=101 -SHOW_LINE_COMMENT=102 -SHOW_MULTILINE_COMMENT=103 -SHOW_WS=104 -FUNCTIONS=105 -META_LINE_COMMENT=106 -META_MULTILINE_COMMENT=107 -META_WS=108 -COLON=109 -SETTING=110 -SETTING_LINE_COMMENT=111 -SETTTING_MULTILINE_COMMENT=112 -SETTING_WS=113 -LOOKUP_LINE_COMMENT=114 -LOOKUP_MULTILINE_COMMENT=115 -LOOKUP_WS=116 -LOOKUP_FIELD_LINE_COMMENT=117 -LOOKUP_FIELD_MULTILINE_COMMENT=118 -LOOKUP_FIELD_WS=119 -METRICS_LINE_COMMENT=120 -METRICS_MULTILINE_COMMENT=121 -METRICS_WS=122 -CLOSING_METRICS_LINE_COMMENT=123 -CLOSING_METRICS_MULTILINE_COMMENT=124 -CLOSING_METRICS_WS=125 +MV_EXPAND=10 +RENAME=11 +ROW=12 +SHOW=13 +SORT=14 +STATS=15 +WHERE=16 +DEV_INLINESTATS=17 +DEV_LOOKUP=18 +DEV_MATCH=19 +DEV_METRICS=20 +UNKNOWN_CMD=21 +LINE_COMMENT=22 +MULTILINE_COMMENT=23 +WS=24 +PIPE=25 +QUOTED_STRING=26 +INTEGER_LITERAL=27 +DECIMAL_LITERAL=28 +BY=29 +AND=30 +ASC=31 +ASSIGN=32 +CAST_OP=33 +COMMA=34 +DESC=35 +DOT=36 +FALSE=37 +FIRST=38 +IN=39 +IS=40 +LAST=41 +LIKE=42 +LP=43 +NOT=44 +NULL=45 +NULLS=46 +OR=47 +PARAM=48 +RLIKE=49 +RP=50 +TRUE=51 +EQ=52 +CIEQ=53 +NEQ=54 +LT=55 +LTE=56 +GT=57 +GTE=58 +PLUS=59 +MINUS=60 +ASTERISK=61 +SLASH=62 +PERCENT=63 +NAMED_OR_POSITIONAL_PARAM=64 +OPENING_BRACKET=65 +CLOSING_BRACKET=66 +UNQUOTED_IDENTIFIER=67 +QUOTED_IDENTIFIER=68 +EXPR_LINE_COMMENT=69 +EXPR_MULTILINE_COMMENT=70 +EXPR_WS=71 +EXPLAIN_WS=72 +EXPLAIN_LINE_COMMENT=73 +EXPLAIN_MULTILINE_COMMENT=74 +METADATA=75 +UNQUOTED_SOURCE=76 +FROM_LINE_COMMENT=77 +FROM_MULTILINE_COMMENT=78 +FROM_WS=79 +ID_PATTERN=80 +PROJECT_LINE_COMMENT=81 +PROJECT_MULTILINE_COMMENT=82 +PROJECT_WS=83 +AS=84 +RENAME_LINE_COMMENT=85 +RENAME_MULTILINE_COMMENT=86 +RENAME_WS=87 +ON=88 +WITH=89 +ENRICH_POLICY_NAME=90 +ENRICH_LINE_COMMENT=91 +ENRICH_MULTILINE_COMMENT=92 +ENRICH_WS=93 +ENRICH_FIELD_LINE_COMMENT=94 +ENRICH_FIELD_MULTILINE_COMMENT=95 +ENRICH_FIELD_WS=96 +MVEXPAND_LINE_COMMENT=97 +MVEXPAND_MULTILINE_COMMENT=98 +MVEXPAND_WS=99 +INFO=100 +SHOW_LINE_COMMENT=101 +SHOW_MULTILINE_COMMENT=102 +SHOW_WS=103 +COLON=104 +SETTING=105 +SETTING_LINE_COMMENT=106 +SETTTING_MULTILINE_COMMENT=107 +SETTING_WS=108 +LOOKUP_LINE_COMMENT=109 +LOOKUP_MULTILINE_COMMENT=110 +LOOKUP_WS=111 +LOOKUP_FIELD_LINE_COMMENT=112 +LOOKUP_FIELD_MULTILINE_COMMENT=113 +LOOKUP_FIELD_WS=114 +METRICS_LINE_COMMENT=115 +METRICS_MULTILINE_COMMENT=116 +METRICS_WS=117 +CLOSING_METRICS_LINE_COMMENT=118 +CLOSING_METRICS_MULTILINE_COMMENT=119 +CLOSING_METRICS_WS=120 'dissect'=1 'drop'=2 'enrich'=3 @@ -132,55 +127,53 @@ CLOSING_METRICS_WS=125 'grok'=7 'keep'=8 'limit'=9 -'meta'=10 -'mv_expand'=11 -'rename'=12 -'row'=13 -'show'=14 -'sort'=15 -'stats'=16 -'where'=17 -'|'=26 -'by'=30 -'and'=31 -'asc'=32 -'='=33 -'::'=34 -','=35 -'desc'=36 -'.'=37 -'false'=38 -'first'=39 -'in'=40 -'is'=41 -'last'=42 -'like'=43 -'('=44 -'not'=45 -'null'=46 -'nulls'=47 -'or'=48 -'?'=49 -'rlike'=50 -')'=51 -'true'=52 -'=='=53 -'=~'=54 -'!='=55 -'<'=56 -'<='=57 -'>'=58 -'>='=59 -'+'=60 -'-'=61 -'*'=62 -'/'=63 -'%'=64 -']'=67 -'metadata'=76 -'as'=85 -'on'=89 -'with'=90 -'info'=101 -'functions'=105 -':'=109 +'mv_expand'=10 +'rename'=11 +'row'=12 +'show'=13 +'sort'=14 +'stats'=15 +'where'=16 +'|'=25 +'by'=29 +'and'=30 +'asc'=31 +'='=32 +'::'=33 +','=34 +'desc'=35 +'.'=36 +'false'=37 +'first'=38 +'in'=39 +'is'=40 +'last'=41 +'like'=42 +'('=43 +'not'=44 +'null'=45 +'nulls'=46 +'or'=47 +'?'=48 +'rlike'=49 +')'=50 +'true'=51 +'=='=52 +'=~'=53 +'!='=54 +'<'=55 +'<='=56 +'>'=57 +'>='=58 +'+'=59 +'-'=60 +'*'=61 +'/'=62 +'%'=63 +']'=66 +'metadata'=75 +'as'=84 +'on'=88 +'with'=89 +'info'=100 +':'=104 diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.ts b/packages/kbn-esql-ast/src/antlr/esql_parser.ts index fd01072600784..41aea98166c97 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.ts @@ -37,122 +37,117 @@ export default class esql_parser extends parser_config { public static readonly GROK = 7; public static readonly KEEP = 8; public static readonly LIMIT = 9; - public static readonly META = 10; - public static readonly MV_EXPAND = 11; - public static readonly RENAME = 12; - public static readonly ROW = 13; - public static readonly SHOW = 14; - public static readonly SORT = 15; - public static readonly STATS = 16; - public static readonly WHERE = 17; - public static readonly DEV_INLINESTATS = 18; - public static readonly DEV_LOOKUP = 19; - public static readonly DEV_MATCH = 20; - public static readonly DEV_METRICS = 21; - public static readonly UNKNOWN_CMD = 22; - public static readonly LINE_COMMENT = 23; - public static readonly MULTILINE_COMMENT = 24; - public static readonly WS = 25; - public static readonly PIPE = 26; - public static readonly QUOTED_STRING = 27; - public static readonly INTEGER_LITERAL = 28; - public static readonly DECIMAL_LITERAL = 29; - public static readonly BY = 30; - public static readonly AND = 31; - public static readonly ASC = 32; - public static readonly ASSIGN = 33; - public static readonly CAST_OP = 34; - public static readonly COMMA = 35; - public static readonly DESC = 36; - public static readonly DOT = 37; - public static readonly FALSE = 38; - public static readonly FIRST = 39; - public static readonly IN = 40; - public static readonly IS = 41; - public static readonly LAST = 42; - public static readonly LIKE = 43; - public static readonly LP = 44; - public static readonly NOT = 45; - public static readonly NULL = 46; - public static readonly NULLS = 47; - public static readonly OR = 48; - public static readonly PARAM = 49; - public static readonly RLIKE = 50; - public static readonly RP = 51; - public static readonly TRUE = 52; - public static readonly EQ = 53; - public static readonly CIEQ = 54; - public static readonly NEQ = 55; - public static readonly LT = 56; - public static readonly LTE = 57; - public static readonly GT = 58; - public static readonly GTE = 59; - public static readonly PLUS = 60; - public static readonly MINUS = 61; - public static readonly ASTERISK = 62; - public static readonly SLASH = 63; - public static readonly PERCENT = 64; - public static readonly NAMED_OR_POSITIONAL_PARAM = 65; - public static readonly OPENING_BRACKET = 66; - public static readonly CLOSING_BRACKET = 67; - public static readonly UNQUOTED_IDENTIFIER = 68; - public static readonly QUOTED_IDENTIFIER = 69; - public static readonly EXPR_LINE_COMMENT = 70; - public static readonly EXPR_MULTILINE_COMMENT = 71; - public static readonly EXPR_WS = 72; - public static readonly EXPLAIN_WS = 73; - public static readonly EXPLAIN_LINE_COMMENT = 74; - public static readonly EXPLAIN_MULTILINE_COMMENT = 75; - public static readonly METADATA = 76; - public static readonly UNQUOTED_SOURCE = 77; - public static readonly FROM_LINE_COMMENT = 78; - public static readonly FROM_MULTILINE_COMMENT = 79; - public static readonly FROM_WS = 80; - public static readonly ID_PATTERN = 81; - public static readonly PROJECT_LINE_COMMENT = 82; - public static readonly PROJECT_MULTILINE_COMMENT = 83; - public static readonly PROJECT_WS = 84; - public static readonly AS = 85; - public static readonly RENAME_LINE_COMMENT = 86; - public static readonly RENAME_MULTILINE_COMMENT = 87; - public static readonly RENAME_WS = 88; - public static readonly ON = 89; - public static readonly WITH = 90; - public static readonly ENRICH_POLICY_NAME = 91; - public static readonly ENRICH_LINE_COMMENT = 92; - public static readonly ENRICH_MULTILINE_COMMENT = 93; - public static readonly ENRICH_WS = 94; - public static readonly ENRICH_FIELD_LINE_COMMENT = 95; - public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 96; - public static readonly ENRICH_FIELD_WS = 97; - public static readonly MVEXPAND_LINE_COMMENT = 98; - public static readonly MVEXPAND_MULTILINE_COMMENT = 99; - public static readonly MVEXPAND_WS = 100; - public static readonly INFO = 101; - public static readonly SHOW_LINE_COMMENT = 102; - public static readonly SHOW_MULTILINE_COMMENT = 103; - public static readonly SHOW_WS = 104; - public static readonly FUNCTIONS = 105; - public static readonly META_LINE_COMMENT = 106; - public static readonly META_MULTILINE_COMMENT = 107; - public static readonly META_WS = 108; - public static readonly COLON = 109; - public static readonly SETTING = 110; - public static readonly SETTING_LINE_COMMENT = 111; - public static readonly SETTTING_MULTILINE_COMMENT = 112; - public static readonly SETTING_WS = 113; - public static readonly LOOKUP_LINE_COMMENT = 114; - public static readonly LOOKUP_MULTILINE_COMMENT = 115; - public static readonly LOOKUP_WS = 116; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 117; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 118; - public static readonly LOOKUP_FIELD_WS = 119; - public static readonly METRICS_LINE_COMMENT = 120; - public static readonly METRICS_MULTILINE_COMMENT = 121; - public static readonly METRICS_WS = 122; - public static readonly CLOSING_METRICS_LINE_COMMENT = 123; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 124; - public static readonly CLOSING_METRICS_WS = 125; + public static readonly MV_EXPAND = 10; + public static readonly RENAME = 11; + public static readonly ROW = 12; + public static readonly SHOW = 13; + public static readonly SORT = 14; + public static readonly STATS = 15; + public static readonly WHERE = 16; + public static readonly DEV_INLINESTATS = 17; + public static readonly DEV_LOOKUP = 18; + public static readonly DEV_MATCH = 19; + public static readonly DEV_METRICS = 20; + public static readonly UNKNOWN_CMD = 21; + public static readonly LINE_COMMENT = 22; + public static readonly MULTILINE_COMMENT = 23; + public static readonly WS = 24; + public static readonly PIPE = 25; + public static readonly QUOTED_STRING = 26; + public static readonly INTEGER_LITERAL = 27; + public static readonly DECIMAL_LITERAL = 28; + public static readonly BY = 29; + public static readonly AND = 30; + public static readonly ASC = 31; + public static readonly ASSIGN = 32; + public static readonly CAST_OP = 33; + public static readonly COMMA = 34; + public static readonly DESC = 35; + public static readonly DOT = 36; + public static readonly FALSE = 37; + public static readonly FIRST = 38; + public static readonly IN = 39; + public static readonly IS = 40; + public static readonly LAST = 41; + public static readonly LIKE = 42; + public static readonly LP = 43; + public static readonly NOT = 44; + public static readonly NULL = 45; + public static readonly NULLS = 46; + public static readonly OR = 47; + public static readonly PARAM = 48; + public static readonly RLIKE = 49; + public static readonly RP = 50; + public static readonly TRUE = 51; + public static readonly EQ = 52; + public static readonly CIEQ = 53; + public static readonly NEQ = 54; + public static readonly LT = 55; + public static readonly LTE = 56; + public static readonly GT = 57; + public static readonly GTE = 58; + public static readonly PLUS = 59; + public static readonly MINUS = 60; + public static readonly ASTERISK = 61; + public static readonly SLASH = 62; + public static readonly PERCENT = 63; + public static readonly NAMED_OR_POSITIONAL_PARAM = 64; + public static readonly OPENING_BRACKET = 65; + public static readonly CLOSING_BRACKET = 66; + public static readonly UNQUOTED_IDENTIFIER = 67; + public static readonly QUOTED_IDENTIFIER = 68; + public static readonly EXPR_LINE_COMMENT = 69; + public static readonly EXPR_MULTILINE_COMMENT = 70; + public static readonly EXPR_WS = 71; + public static readonly EXPLAIN_WS = 72; + public static readonly EXPLAIN_LINE_COMMENT = 73; + public static readonly EXPLAIN_MULTILINE_COMMENT = 74; + public static readonly METADATA = 75; + public static readonly UNQUOTED_SOURCE = 76; + public static readonly FROM_LINE_COMMENT = 77; + public static readonly FROM_MULTILINE_COMMENT = 78; + public static readonly FROM_WS = 79; + public static readonly ID_PATTERN = 80; + public static readonly PROJECT_LINE_COMMENT = 81; + public static readonly PROJECT_MULTILINE_COMMENT = 82; + public static readonly PROJECT_WS = 83; + public static readonly AS = 84; + public static readonly RENAME_LINE_COMMENT = 85; + public static readonly RENAME_MULTILINE_COMMENT = 86; + public static readonly RENAME_WS = 87; + public static readonly ON = 88; + public static readonly WITH = 89; + public static readonly ENRICH_POLICY_NAME = 90; + public static readonly ENRICH_LINE_COMMENT = 91; + public static readonly ENRICH_MULTILINE_COMMENT = 92; + public static readonly ENRICH_WS = 93; + public static readonly ENRICH_FIELD_LINE_COMMENT = 94; + public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 95; + public static readonly ENRICH_FIELD_WS = 96; + public static readonly MVEXPAND_LINE_COMMENT = 97; + public static readonly MVEXPAND_MULTILINE_COMMENT = 98; + public static readonly MVEXPAND_WS = 99; + public static readonly INFO = 100; + public static readonly SHOW_LINE_COMMENT = 101; + public static readonly SHOW_MULTILINE_COMMENT = 102; + public static readonly SHOW_WS = 103; + public static readonly COLON = 104; + public static readonly SETTING = 105; + public static readonly SETTING_LINE_COMMENT = 106; + public static readonly SETTTING_MULTILINE_COMMENT = 107; + public static readonly SETTING_WS = 108; + public static readonly LOOKUP_LINE_COMMENT = 109; + public static readonly LOOKUP_MULTILINE_COMMENT = 110; + public static readonly LOOKUP_WS = 111; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 112; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 113; + public static readonly LOOKUP_FIELD_WS = 114; + public static readonly METRICS_LINE_COMMENT = 115; + public static readonly METRICS_MULTILINE_COMMENT = 116; + public static readonly METRICS_WS = 117; + public static readonly CLOSING_METRICS_LINE_COMMENT = 118; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 119; + public static readonly CLOSING_METRICS_WS = 120; public static override readonly EOF = Token.EOF; public static readonly RULE_singleStatement = 0; public static readonly RULE_query = 1; @@ -186,29 +181,29 @@ export default class esql_parser extends parser_config { public static readonly RULE_identifier = 29; public static readonly RULE_identifierPattern = 30; public static readonly RULE_constant = 31; - public static readonly RULE_params = 32; - public static readonly RULE_limitCommand = 33; - public static readonly RULE_sortCommand = 34; - public static readonly RULE_orderExpression = 35; - public static readonly RULE_keepCommand = 36; - public static readonly RULE_dropCommand = 37; - public static readonly RULE_renameCommand = 38; - public static readonly RULE_renameClause = 39; - public static readonly RULE_dissectCommand = 40; - public static readonly RULE_grokCommand = 41; - public static readonly RULE_mvExpandCommand = 42; - public static readonly RULE_commandOptions = 43; - public static readonly RULE_commandOption = 44; - public static readonly RULE_booleanValue = 45; - public static readonly RULE_numericValue = 46; - public static readonly RULE_decimalValue = 47; - public static readonly RULE_integerValue = 48; - public static readonly RULE_string = 49; - public static readonly RULE_comparisonOperator = 50; - public static readonly RULE_explainCommand = 51; - public static readonly RULE_subqueryExpression = 52; - public static readonly RULE_showCommand = 53; - public static readonly RULE_metaCommand = 54; + public static readonly RULE_parameter = 32; + public static readonly RULE_identifierOrParameter = 33; + public static readonly RULE_limitCommand = 34; + public static readonly RULE_sortCommand = 35; + public static readonly RULE_orderExpression = 36; + public static readonly RULE_keepCommand = 37; + public static readonly RULE_dropCommand = 38; + public static readonly RULE_renameCommand = 39; + public static readonly RULE_renameClause = 40; + public static readonly RULE_dissectCommand = 41; + public static readonly RULE_grokCommand = 42; + public static readonly RULE_mvExpandCommand = 43; + public static readonly RULE_commandOptions = 44; + public static readonly RULE_commandOption = 45; + public static readonly RULE_booleanValue = 46; + public static readonly RULE_numericValue = 47; + public static readonly RULE_decimalValue = 48; + public static readonly RULE_integerValue = 49; + public static readonly RULE_string = 50; + public static readonly RULE_comparisonOperator = 51; + public static readonly RULE_explainCommand = 52; + public static readonly RULE_subqueryExpression = 53; + public static readonly RULE_showCommand = 54; public static readonly RULE_enrichCommand = 55; public static readonly RULE_enrichWithClause = 56; public static readonly RULE_lookupCommand = 57; @@ -218,7 +213,7 @@ export default class esql_parser extends parser_config { "'eval'", "'explain'", "'from'", "'grok'", "'keep'", "'limit'", - "'meta'", "'mv_expand'", + "'mv_expand'", "'rename'", "'row'", "'show'", "'sort'", "'stats'", @@ -266,15 +261,13 @@ export default class esql_parser extends parser_config { null, null, "'info'", null, null, null, - "'functions'", - null, null, - null, "':'" ]; + "':'" ]; public static readonly symbolicNames: (string | null)[] = [ null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", "KEEP", "LIMIT", - "META", "MV_EXPAND", + "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", "WHERE", @@ -344,10 +337,6 @@ export default class esql_parser extends parser_config { "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS", - "FUNCTIONS", - "META_LINE_COMMENT", - "META_MULTILINE_COMMENT", - "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", @@ -373,12 +362,12 @@ export default class esql_parser extends parser_config { "clusterString", "indexString", "metadata", "metadataOption", "deprecated_metadata", "metricsCommand", "evalCommand", "statsCommand", "qualifiedName", "qualifiedNamePattern", "qualifiedNamePatterns", "identifier", "identifierPattern", "constant", - "params", "limitCommand", "sortCommand", "orderExpression", "keepCommand", - "dropCommand", "renameCommand", "renameClause", "dissectCommand", "grokCommand", - "mvExpandCommand", "commandOptions", "commandOption", "booleanValue", + "parameter", "identifierOrParameter", "limitCommand", "sortCommand", "orderExpression", + "keepCommand", "dropCommand", "renameCommand", "renameClause", "dissectCommand", + "grokCommand", "mvExpandCommand", "commandOptions", "commandOption", "booleanValue", "numericValue", "decimalValue", "integerValue", "string", "comparisonOperator", - "explainCommand", "subqueryExpression", "showCommand", "metaCommand", - "enrichCommand", "enrichWithClause", "lookupCommand", "inlinestatsCommand", + "explainCommand", "subqueryExpression", "showCommand", "enrichCommand", + "enrichWithClause", "lookupCommand", "inlinestatsCommand", ]; public get grammarFileName(): string { return "esql_parser.g4"; } public get literalNames(): (string | null)[] { return esql_parser.literalNames; } @@ -498,7 +487,7 @@ export default class esql_parser extends parser_config { let localctx: SourceCommandContext = new SourceCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 4, esql_parser.RULE_sourceCommand); try { - this.state = 139; + this.state = 138; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 1, this._ctx) ) { case 1: @@ -519,31 +508,24 @@ export default class esql_parser extends parser_config { this.enterOuterAlt(localctx, 3); { this.state = 134; - this.metaCommand(); + this.rowCommand(); } break; case 4: this.enterOuterAlt(localctx, 4); { this.state = 135; - this.rowCommand(); + this.showCommand(); } break; case 5: this.enterOuterAlt(localctx, 5); { this.state = 136; - this.showCommand(); - } - break; - case 6: - this.enterOuterAlt(localctx, 6); - { - this.state = 137; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 138; + this.state = 137; this.metricsCommand(); } break; @@ -568,112 +550,112 @@ export default class esql_parser extends parser_config { let localctx: ProcessingCommandContext = new ProcessingCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 6, esql_parser.RULE_processingCommand); try { - this.state = 157; + this.state = 156; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 2, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 141; + this.state = 140; this.evalCommand(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 142; + this.state = 141; this.whereCommand(); } break; case 3: this.enterOuterAlt(localctx, 3); { - this.state = 143; + this.state = 142; this.keepCommand(); } break; case 4: this.enterOuterAlt(localctx, 4); { - this.state = 144; + this.state = 143; this.limitCommand(); } break; case 5: this.enterOuterAlt(localctx, 5); { - this.state = 145; + this.state = 144; this.statsCommand(); } break; case 6: this.enterOuterAlt(localctx, 6); { - this.state = 146; + this.state = 145; this.sortCommand(); } break; case 7: this.enterOuterAlt(localctx, 7); { - this.state = 147; + this.state = 146; this.dropCommand(); } break; case 8: this.enterOuterAlt(localctx, 8); { - this.state = 148; + this.state = 147; this.renameCommand(); } break; case 9: this.enterOuterAlt(localctx, 9); { - this.state = 149; + this.state = 148; this.dissectCommand(); } break; case 10: this.enterOuterAlt(localctx, 10); { - this.state = 150; + this.state = 149; this.grokCommand(); } break; case 11: this.enterOuterAlt(localctx, 11); { - this.state = 151; + this.state = 150; this.enrichCommand(); } break; case 12: this.enterOuterAlt(localctx, 12); { - this.state = 152; + this.state = 151; this.mvExpandCommand(); } break; case 13: this.enterOuterAlt(localctx, 13); { - this.state = 153; + this.state = 152; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 154; + this.state = 153; this.inlinestatsCommand(); } break; case 14: this.enterOuterAlt(localctx, 14); { - this.state = 155; + this.state = 154; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 156; + this.state = 155; this.lookupCommand(); } break; @@ -700,9 +682,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 159; + this.state = 158; this.match(esql_parser.WHERE); - this.state = 160; + this.state = 159; this.booleanExpression(0); } } @@ -740,7 +722,7 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 192; + this.state = 191; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 6, this._ctx) ) { case 1: @@ -749,9 +731,9 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 163; + this.state = 162; this.match(esql_parser.NOT); - this.state = 164; + this.state = 163; this.booleanExpression(8); } break; @@ -760,7 +742,7 @@ export default class esql_parser extends parser_config { localctx = new BooleanDefaultContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 165; + this.state = 164; this.valueExpression(); } break; @@ -769,7 +751,7 @@ export default class esql_parser extends parser_config { localctx = new RegexExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 166; + this.state = 165; this.regexBooleanExpression(); } break; @@ -778,41 +760,41 @@ export default class esql_parser extends parser_config { localctx = new LogicalInContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 167; + this.state = 166; this.valueExpression(); - this.state = 169; + this.state = 168; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===45) { + if (_la===44) { { - this.state = 168; + this.state = 167; this.match(esql_parser.NOT); } } - this.state = 171; + this.state = 170; this.match(esql_parser.IN); - this.state = 172; + this.state = 171; this.match(esql_parser.LP); - this.state = 173; + this.state = 172; this.valueExpression(); - this.state = 178; + this.state = 177; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===35) { + while (_la===34) { { { - this.state = 174; + this.state = 173; this.match(esql_parser.COMMA); - this.state = 175; + this.state = 174; this.valueExpression(); } } - this.state = 180; + this.state = 179; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 181; + this.state = 180; this.match(esql_parser.RP); } break; @@ -821,21 +803,21 @@ export default class esql_parser extends parser_config { localctx = new IsNullContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 183; + this.state = 182; this.valueExpression(); - this.state = 184; + this.state = 183; this.match(esql_parser.IS); - this.state = 186; + this.state = 185; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===45) { + if (_la===44) { { - this.state = 185; + this.state = 184; this.match(esql_parser.NOT); } } - this.state = 188; + this.state = 187; this.match(esql_parser.NULL); } break; @@ -844,17 +826,17 @@ export default class esql_parser extends parser_config { localctx = new MatchExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 190; + this.state = 189; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 191; + this.state = 190; this.matchBooleanExpression(); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 202; + this.state = 201; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 8, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -864,7 +846,7 @@ export default class esql_parser extends parser_config { } _prevctx = localctx; { - this.state = 200; + this.state = 199; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 7, this._ctx) ) { case 1: @@ -872,13 +854,13 @@ export default class esql_parser extends parser_config { localctx = new LogicalBinaryContext(this, new BooleanExpressionContext(this, _parentctx, _parentState)); (localctx as LogicalBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 194; + this.state = 193; if (!(this.precpred(this._ctx, 5))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 5)"); } - this.state = 195; + this.state = 194; (localctx as LogicalBinaryContext)._operator = this.match(esql_parser.AND); - this.state = 196; + this.state = 195; (localctx as LogicalBinaryContext)._right = this.booleanExpression(6); } break; @@ -887,20 +869,20 @@ export default class esql_parser extends parser_config { localctx = new LogicalBinaryContext(this, new BooleanExpressionContext(this, _parentctx, _parentState)); (localctx as LogicalBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 197; + this.state = 196; if (!(this.precpred(this._ctx, 4))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 4)"); } - this.state = 198; + this.state = 197; (localctx as LogicalBinaryContext)._operator = this.match(esql_parser.OR); - this.state = 199; + this.state = 198; (localctx as LogicalBinaryContext)._right = this.booleanExpression(5); } break; } } } - this.state = 204; + this.state = 203; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 8, this._ctx); } @@ -926,48 +908,48 @@ export default class esql_parser extends parser_config { this.enterRule(localctx, 12, esql_parser.RULE_regexBooleanExpression); let _la: number; try { - this.state = 219; + this.state = 218; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 11, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 205; + this.state = 204; this.valueExpression(); - this.state = 207; + this.state = 206; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===45) { + if (_la===44) { { - this.state = 206; + this.state = 205; this.match(esql_parser.NOT); } } - this.state = 209; + this.state = 208; localctx._kind = this.match(esql_parser.LIKE); - this.state = 210; + this.state = 209; localctx._pattern = this.string_(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 212; + this.state = 211; this.valueExpression(); - this.state = 214; + this.state = 213; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===45) { + if (_la===44) { { - this.state = 213; + this.state = 212; this.match(esql_parser.NOT); } } - this.state = 216; + this.state = 215; localctx._kind = this.match(esql_parser.RLIKE); - this.state = 217; + this.state = 216; localctx._pattern = this.string_(); } break; @@ -994,11 +976,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 221; + this.state = 220; this.valueExpression(); - this.state = 222; + this.state = 221; this.match(esql_parser.DEV_MATCH); - this.state = 223; + this.state = 222; localctx._queryString = this.string_(); } } @@ -1021,14 +1003,14 @@ export default class esql_parser extends parser_config { let localctx: ValueExpressionContext = new ValueExpressionContext(this, this._ctx, this.state); this.enterRule(localctx, 16, esql_parser.RULE_valueExpression); try { - this.state = 230; + this.state = 229; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 12, this._ctx) ) { case 1: localctx = new ValueExpressionDefaultContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 225; + this.state = 224; this.operatorExpression(0); } break; @@ -1036,11 +1018,11 @@ export default class esql_parser extends parser_config { localctx = new ComparisonContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 226; + this.state = 225; (localctx as ComparisonContext)._left = this.operatorExpression(0); - this.state = 227; + this.state = 226; this.comparisonOperator(); - this.state = 228; + this.state = 227; (localctx as ComparisonContext)._right = this.operatorExpression(0); } break; @@ -1080,7 +1062,7 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 236; + this.state = 235; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 13, this._ctx) ) { case 1: @@ -1089,7 +1071,7 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 233; + this.state = 232; this.primaryExpression(0); } break; @@ -1098,23 +1080,23 @@ export default class esql_parser extends parser_config { localctx = new ArithmeticUnaryContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 234; + this.state = 233; (localctx as ArithmeticUnaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===60 || _la===61)) { + if(!(_la===59 || _la===60)) { (localctx as ArithmeticUnaryContext)._operator = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 235; + this.state = 234; this.operatorExpression(3); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 246; + this.state = 245; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 15, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -1124,7 +1106,7 @@ export default class esql_parser extends parser_config { } _prevctx = localctx; { - this.state = 244; + this.state = 243; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 14, this._ctx) ) { case 1: @@ -1132,21 +1114,21 @@ export default class esql_parser extends parser_config { localctx = new ArithmeticBinaryContext(this, new OperatorExpressionContext(this, _parentctx, _parentState)); (localctx as ArithmeticBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 238; + this.state = 237; if (!(this.precpred(this._ctx, 2))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 2)"); } - this.state = 239; + this.state = 238; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(((((_la - 62)) & ~0x1F) === 0 && ((1 << (_la - 62)) & 7) !== 0))) { + if(!(((((_la - 61)) & ~0x1F) === 0 && ((1 << (_la - 61)) & 7) !== 0))) { (localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 240; + this.state = 239; (localctx as ArithmeticBinaryContext)._right = this.operatorExpression(3); } break; @@ -1155,28 +1137,28 @@ export default class esql_parser extends parser_config { localctx = new ArithmeticBinaryContext(this, new OperatorExpressionContext(this, _parentctx, _parentState)); (localctx as ArithmeticBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 241; + this.state = 240; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 242; + this.state = 241; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===60 || _la===61)) { + if(!(_la===59 || _la===60)) { (localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 243; + this.state = 242; (localctx as ArithmeticBinaryContext)._right = this.operatorExpression(2); } break; } } } - this.state = 248; + this.state = 247; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 15, this._ctx); } @@ -1215,7 +1197,7 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 257; + this.state = 256; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 16, this._ctx) ) { case 1: @@ -1224,7 +1206,7 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 250; + this.state = 249; this.constant(); } break; @@ -1233,7 +1215,7 @@ export default class esql_parser extends parser_config { localctx = new DereferenceContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 251; + this.state = 250; this.qualifiedName(); } break; @@ -1242,7 +1224,7 @@ export default class esql_parser extends parser_config { localctx = new FunctionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 252; + this.state = 251; this.functionExpression(); } break; @@ -1251,17 +1233,17 @@ export default class esql_parser extends parser_config { localctx = new ParenthesizedExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 253; + this.state = 252; this.match(esql_parser.LP); - this.state = 254; + this.state = 253; this.booleanExpression(0); - this.state = 255; + this.state = 254; this.match(esql_parser.RP); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 264; + this.state = 263; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 17, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -1274,18 +1256,18 @@ export default class esql_parser extends parser_config { { localctx = new InlineCastContext(this, new PrimaryExpressionContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_primaryExpression); - this.state = 259; + this.state = 258; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 260; + this.state = 259; this.match(esql_parser.CAST_OP); - this.state = 261; + this.state = 260; this.dataType(); } } } - this.state = 266; + this.state = 265; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 17, this._ctx); } @@ -1313,37 +1295,37 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { + this.state = 266; + this.identifierOrParameter(); this.state = 267; - this.identifier(); - this.state = 268; this.match(esql_parser.LP); - this.state = 278; + this.state = 277; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 19, this._ctx) ) { case 1: { - this.state = 269; + this.state = 268; this.match(esql_parser.ASTERISK); } break; case 2: { { - this.state = 270; + this.state = 269; this.booleanExpression(0); - this.state = 275; + this.state = 274; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===35) { + while (_la===34) { { { - this.state = 271; + this.state = 270; this.match(esql_parser.COMMA); - this.state = 272; + this.state = 271; this.booleanExpression(0); } } - this.state = 277; + this.state = 276; this._errHandler.sync(this); _la = this._input.LA(1); } @@ -1351,7 +1333,7 @@ export default class esql_parser extends parser_config { } break; } - this.state = 280; + this.state = 279; this.match(esql_parser.RP); } } @@ -1377,7 +1359,7 @@ export default class esql_parser extends parser_config { localctx = new ToDataTypeContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 282; + this.state = 281; this.identifier(); } } @@ -1402,9 +1384,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 284; + this.state = 283; this.match(esql_parser.ROW); - this.state = 285; + this.state = 284; this.fields(); } } @@ -1430,23 +1412,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 287; + this.state = 286; this.field(); - this.state = 292; + this.state = 291; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 288; + this.state = 287; this.match(esql_parser.COMMA); - this.state = 289; + this.state = 288; this.field(); } } } - this.state = 294; + this.state = 293; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); } @@ -1471,24 +1453,24 @@ export default class esql_parser extends parser_config { let localctx: FieldContext = new FieldContext(this, this._ctx, this.state); this.enterRule(localctx, 30, esql_parser.RULE_field); try { - this.state = 300; + this.state = 299; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 21, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 295; + this.state = 294; this.booleanExpression(0); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 296; + this.state = 295; this.qualifiedName(); - this.state = 297; + this.state = 296; this.match(esql_parser.ASSIGN); - this.state = 298; + this.state = 297; this.booleanExpression(0); } break; @@ -1516,34 +1498,34 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 302; + this.state = 301; this.match(esql_parser.FROM); - this.state = 303; + this.state = 302; this.indexPattern(); - this.state = 308; + this.state = 307; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 304; + this.state = 303; this.match(esql_parser.COMMA); - this.state = 305; + this.state = 304; this.indexPattern(); } } } - this.state = 310; + this.state = 309; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); } - this.state = 312; + this.state = 311; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 23, this._ctx) ) { case 1: { - this.state = 311; + this.state = 310; this.metadata(); } break; @@ -1569,24 +1551,24 @@ export default class esql_parser extends parser_config { let localctx: IndexPatternContext = new IndexPatternContext(this, this._ctx, this.state); this.enterRule(localctx, 34, esql_parser.RULE_indexPattern); try { - this.state = 319; + this.state = 318; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 24, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 314; + this.state = 313; this.clusterString(); - this.state = 315; + this.state = 314; this.match(esql_parser.COLON); - this.state = 316; + this.state = 315; this.indexString(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 318; + this.state = 317; this.indexString(); } break; @@ -1613,7 +1595,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 321; + this.state = 320; this.match(esql_parser.UNQUOTED_SOURCE); } } @@ -1639,9 +1621,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 323; + this.state = 322; _la = this._input.LA(1); - if(!(_la===27 || _la===77)) { + if(!(_la===26 || _la===76)) { this._errHandler.recoverInline(this); } else { @@ -1669,20 +1651,20 @@ export default class esql_parser extends parser_config { let localctx: MetadataContext = new MetadataContext(this, this._ctx, this.state); this.enterRule(localctx, 40, esql_parser.RULE_metadata); try { - this.state = 327; + this.state = 326; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 76: + case 75: this.enterOuterAlt(localctx, 1); { - this.state = 325; + this.state = 324; this.metadataOption(); } break; - case 66: + case 65: this.enterOuterAlt(localctx, 2); { - this.state = 326; + this.state = 325; this.deprecated_metadata(); } break; @@ -1712,25 +1694,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 329; + this.state = 328; this.match(esql_parser.METADATA); - this.state = 330; + this.state = 329; this.match(esql_parser.UNQUOTED_SOURCE); - this.state = 335; + this.state = 334; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 331; + this.state = 330; this.match(esql_parser.COMMA); - this.state = 332; + this.state = 331; this.match(esql_parser.UNQUOTED_SOURCE); } } } - this.state = 337; + this.state = 336; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); } @@ -1757,11 +1739,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 338; + this.state = 337; this.match(esql_parser.OPENING_BRACKET); - this.state = 339; + this.state = 338; this.metadataOption(); - this.state = 340; + this.state = 339; this.match(esql_parser.CLOSING_BRACKET); } } @@ -1787,46 +1769,46 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 342; + this.state = 341; this.match(esql_parser.DEV_METRICS); - this.state = 343; + this.state = 342; this.indexPattern(); - this.state = 348; + this.state = 347; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 344; + this.state = 343; this.match(esql_parser.COMMA); - this.state = 345; + this.state = 344; this.indexPattern(); } } } - this.state = 350; + this.state = 349; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); } - this.state = 352; + this.state = 351; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 28, this._ctx) ) { case 1: { - this.state = 351; + this.state = 350; localctx._aggregates = this.fields(); } break; } - this.state = 356; + this.state = 355; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 29, this._ctx) ) { case 1: { - this.state = 354; + this.state = 353; this.match(esql_parser.BY); - this.state = 355; + this.state = 354; localctx._grouping = this.fields(); } break; @@ -1854,9 +1836,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 358; + this.state = 357; this.match(esql_parser.EVAL); - this.state = 359; + this.state = 358; this.fields(); } } @@ -1881,26 +1863,26 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 361; + this.state = 360; this.match(esql_parser.STATS); - this.state = 363; + this.state = 362; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 30, this._ctx) ) { case 1: { - this.state = 362; + this.state = 361; localctx._stats = this.fields(); } break; } - this.state = 367; + this.state = 366; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 31, this._ctx) ) { case 1: { - this.state = 365; + this.state = 364; this.match(esql_parser.BY); - this.state = 366; + this.state = 365; localctx._grouping = this.fields(); } break; @@ -1929,23 +1911,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 369; - this.identifier(); - this.state = 374; + this.state = 368; + this.identifierOrParameter(); + this.state = 373; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 370; + this.state = 369; this.match(esql_parser.DOT); - this.state = 371; - this.identifier(); + this.state = 370; + this.identifierOrParameter(); } } } - this.state = 376; + this.state = 375; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); } @@ -1973,23 +1955,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 377; + this.state = 376; this.identifierPattern(); - this.state = 382; + this.state = 381; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 33, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 378; + this.state = 377; this.match(esql_parser.DOT); - this.state = 379; + this.state = 378; this.identifierPattern(); } } } - this.state = 384; + this.state = 383; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 33, this._ctx); } @@ -2017,23 +1999,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 385; + this.state = 384; this.qualifiedNamePattern(); - this.state = 390; + this.state = 389; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 386; + this.state = 385; this.match(esql_parser.COMMA); - this.state = 387; + this.state = 386; this.qualifiedNamePattern(); } } } - this.state = 392; + this.state = 391; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); } @@ -2061,9 +2043,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 393; + this.state = 392; _la = this._input.LA(1); - if(!(_la===68 || _la===69)) { + if(!(_la===67 || _la===68)) { this._errHandler.recoverInline(this); } else { @@ -2091,10 +2073,26 @@ export default class esql_parser extends parser_config { let localctx: IdentifierPatternContext = new IdentifierPatternContext(this, this._ctx, this.state); this.enterRule(localctx, 60, esql_parser.RULE_identifierPattern); try { - this.enterOuterAlt(localctx, 1); - { - this.state = 395; - this.match(esql_parser.ID_PATTERN); + this.state = 396; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case 80: + this.enterOuterAlt(localctx, 1); + { + this.state = 394; + this.match(esql_parser.ID_PATTERN); + } + break; + case 48: + case 64: + this.enterOuterAlt(localctx, 2); + { + this.state = 395; + this.parameter(); + } + break; + default: + throw new NoViableAltException(this); } } catch (re) { @@ -2117,14 +2115,14 @@ export default class esql_parser extends parser_config { this.enterRule(localctx, 62, esql_parser.RULE_constant); let _la: number; try { - this.state = 439; + this.state = 440; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 38, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 39, this._ctx) ) { case 1: localctx = new NullLiteralContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 397; + this.state = 398; this.match(esql_parser.NULL); } break; @@ -2132,9 +2130,9 @@ export default class esql_parser extends parser_config { localctx = new QualifiedIntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 398; - this.integerValue(); this.state = 399; + this.integerValue(); + this.state = 400; this.match(esql_parser.UNQUOTED_IDENTIFIER); } break; @@ -2142,7 +2140,7 @@ export default class esql_parser extends parser_config { localctx = new DecimalLiteralContext(this, localctx); this.enterOuterAlt(localctx, 3); { - this.state = 401; + this.state = 402; this.decimalValue(); } break; @@ -2150,7 +2148,7 @@ export default class esql_parser extends parser_config { localctx = new IntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 4); { - this.state = 402; + this.state = 403; this.integerValue(); } break; @@ -2158,23 +2156,23 @@ export default class esql_parser extends parser_config { localctx = new BooleanLiteralContext(this, localctx); this.enterOuterAlt(localctx, 5); { - this.state = 403; + this.state = 404; this.booleanValue(); } break; case 6: - localctx = new InputParamsContext(this, localctx); + localctx = new InputParameterContext(this, localctx); this.enterOuterAlt(localctx, 6); { - this.state = 404; - this.params(); + this.state = 405; + this.parameter(); } break; case 7: localctx = new StringLiteralContext(this, localctx); this.enterOuterAlt(localctx, 7); { - this.state = 405; + this.state = 406; this.string_(); } break; @@ -2182,27 +2180,27 @@ export default class esql_parser extends parser_config { localctx = new NumericArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 8); { - this.state = 406; - this.match(esql_parser.OPENING_BRACKET); this.state = 407; + this.match(esql_parser.OPENING_BRACKET); + this.state = 408; this.numericValue(); - this.state = 412; + this.state = 413; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===35) { + while (_la===34) { { { - this.state = 408; - this.match(esql_parser.COMMA); this.state = 409; + this.match(esql_parser.COMMA); + this.state = 410; this.numericValue(); } } - this.state = 414; + this.state = 415; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 415; + this.state = 416; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2210,27 +2208,27 @@ export default class esql_parser extends parser_config { localctx = new BooleanArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 9); { - this.state = 417; - this.match(esql_parser.OPENING_BRACKET); this.state = 418; + this.match(esql_parser.OPENING_BRACKET); + this.state = 419; this.booleanValue(); - this.state = 423; + this.state = 424; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===35) { + while (_la===34) { { { - this.state = 419; - this.match(esql_parser.COMMA); this.state = 420; + this.match(esql_parser.COMMA); + this.state = 421; this.booleanValue(); } } - this.state = 425; + this.state = 426; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 426; + this.state = 427; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2238,27 +2236,27 @@ export default class esql_parser extends parser_config { localctx = new StringArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 10); { - this.state = 428; - this.match(esql_parser.OPENING_BRACKET); this.state = 429; + this.match(esql_parser.OPENING_BRACKET); + this.state = 430; this.string_(); - this.state = 434; + this.state = 435; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===35) { + while (_la===34) { { { - this.state = 430; - this.match(esql_parser.COMMA); this.state = 431; + this.match(esql_parser.COMMA); + this.state = 432; this.string_(); } } - this.state = 436; + this.state = 437; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 437; + this.state = 438; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2279,26 +2277,26 @@ export default class esql_parser extends parser_config { return localctx; } // @RuleVersion(0) - public params(): ParamsContext { - let localctx: ParamsContext = new ParamsContext(this, this._ctx, this.state); - this.enterRule(localctx, 64, esql_parser.RULE_params); + public parameter(): ParameterContext { + let localctx: ParameterContext = new ParameterContext(this, this._ctx, this.state); + this.enterRule(localctx, 64, esql_parser.RULE_parameter); try { - this.state = 443; + this.state = 444; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 49: + case 48: localctx = new InputParamContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 441; + this.state = 442; this.match(esql_parser.PARAM); } break; - case 65: + case 64: localctx = new InputNamedOrPositionalParamContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 442; + this.state = 443; this.match(esql_parser.NAMED_OR_POSITIONAL_PARAM); } break; @@ -2321,15 +2319,57 @@ export default class esql_parser extends parser_config { return localctx; } // @RuleVersion(0) + public identifierOrParameter(): IdentifierOrParameterContext { + let localctx: IdentifierOrParameterContext = new IdentifierOrParameterContext(this, this._ctx, this.state); + this.enterRule(localctx, 66, esql_parser.RULE_identifierOrParameter); + try { + this.state = 448; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case 67: + case 68: + this.enterOuterAlt(localctx, 1); + { + this.state = 446; + this.identifier(); + } + break; + case 48: + case 64: + this.enterOuterAlt(localctx, 2); + { + this.state = 447; + this.parameter(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) public limitCommand(): LimitCommandContext { let localctx: LimitCommandContext = new LimitCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 66, esql_parser.RULE_limitCommand); + this.enterRule(localctx, 68, esql_parser.RULE_limitCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 445; + this.state = 450; this.match(esql_parser.LIMIT); - this.state = 446; + this.state = 451; this.match(esql_parser.INTEGER_LITERAL); } } @@ -2350,32 +2390,32 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public sortCommand(): SortCommandContext { let localctx: SortCommandContext = new SortCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 68, esql_parser.RULE_sortCommand); + this.enterRule(localctx, 70, esql_parser.RULE_sortCommand); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 448; + this.state = 453; this.match(esql_parser.SORT); - this.state = 449; - this.orderExpression(); this.state = 454; + this.orderExpression(); + this.state = 459; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 40, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 42, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 450; + this.state = 455; this.match(esql_parser.COMMA); - this.state = 451; + this.state = 456; this.orderExpression(); } } } - this.state = 456; + this.state = 461; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 40, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 42, this._ctx); } } } @@ -2396,22 +2436,22 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public orderExpression(): OrderExpressionContext { let localctx: OrderExpressionContext = new OrderExpressionContext(this, this._ctx, this.state); - this.enterRule(localctx, 70, esql_parser.RULE_orderExpression); + this.enterRule(localctx, 72, esql_parser.RULE_orderExpression); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 457; + this.state = 462; this.booleanExpression(0); - this.state = 459; + this.state = 464; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 41, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 43, this._ctx) ) { case 1: { - this.state = 458; + this.state = 463; localctx._ordering = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===32 || _la===36)) { + if(!(_la===31 || _la===35)) { localctx._ordering = this._errHandler.recoverInline(this); } else { @@ -2421,17 +2461,17 @@ export default class esql_parser extends parser_config { } break; } - this.state = 463; + this.state = 468; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 42, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 44, this._ctx) ) { case 1: { - this.state = 461; + this.state = 466; this.match(esql_parser.NULLS); - this.state = 462; + this.state = 467; localctx._nullOrdering = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===39 || _la===42)) { + if(!(_la===38 || _la===41)) { localctx._nullOrdering = this._errHandler.recoverInline(this); } else { @@ -2460,13 +2500,13 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public keepCommand(): KeepCommandContext { let localctx: KeepCommandContext = new KeepCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 72, esql_parser.RULE_keepCommand); + this.enterRule(localctx, 74, esql_parser.RULE_keepCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 465; + this.state = 470; this.match(esql_parser.KEEP); - this.state = 466; + this.state = 471; this.qualifiedNamePatterns(); } } @@ -2487,13 +2527,13 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public dropCommand(): DropCommandContext { let localctx: DropCommandContext = new DropCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 74, esql_parser.RULE_dropCommand); + this.enterRule(localctx, 76, esql_parser.RULE_dropCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 468; + this.state = 473; this.match(esql_parser.DROP); - this.state = 469; + this.state = 474; this.qualifiedNamePatterns(); } } @@ -2514,32 +2554,32 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public renameCommand(): RenameCommandContext { let localctx: RenameCommandContext = new RenameCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 76, esql_parser.RULE_renameCommand); + this.enterRule(localctx, 78, esql_parser.RULE_renameCommand); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 471; + this.state = 476; this.match(esql_parser.RENAME); - this.state = 472; - this.renameClause(); this.state = 477; + this.renameClause(); + this.state = 482; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 43, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 473; + this.state = 478; this.match(esql_parser.COMMA); - this.state = 474; + this.state = 479; this.renameClause(); } } } - this.state = 479; + this.state = 484; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 43, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); } } } @@ -2560,15 +2600,15 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public renameClause(): RenameClauseContext { let localctx: RenameClauseContext = new RenameClauseContext(this, this._ctx, this.state); - this.enterRule(localctx, 78, esql_parser.RULE_renameClause); + this.enterRule(localctx, 80, esql_parser.RULE_renameClause); try { this.enterOuterAlt(localctx, 1); { - this.state = 480; + this.state = 485; localctx._oldName = this.qualifiedNamePattern(); - this.state = 481; + this.state = 486; this.match(esql_parser.AS); - this.state = 482; + this.state = 487; localctx._newName = this.qualifiedNamePattern(); } } @@ -2589,22 +2629,22 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public dissectCommand(): DissectCommandContext { let localctx: DissectCommandContext = new DissectCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 80, esql_parser.RULE_dissectCommand); + this.enterRule(localctx, 82, esql_parser.RULE_dissectCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 484; + this.state = 489; this.match(esql_parser.DISSECT); - this.state = 485; + this.state = 490; this.primaryExpression(0); - this.state = 486; + this.state = 491; this.string_(); - this.state = 488; + this.state = 493; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 44, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 46, this._ctx) ) { case 1: { - this.state = 487; + this.state = 492; this.commandOptions(); } break; @@ -2628,15 +2668,15 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public grokCommand(): GrokCommandContext { let localctx: GrokCommandContext = new GrokCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 82, esql_parser.RULE_grokCommand); + this.enterRule(localctx, 84, esql_parser.RULE_grokCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 490; + this.state = 495; this.match(esql_parser.GROK); - this.state = 491; + this.state = 496; this.primaryExpression(0); - this.state = 492; + this.state = 497; this.string_(); } } @@ -2657,13 +2697,13 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public mvExpandCommand(): MvExpandCommandContext { let localctx: MvExpandCommandContext = new MvExpandCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 84, esql_parser.RULE_mvExpandCommand); + this.enterRule(localctx, 86, esql_parser.RULE_mvExpandCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 494; + this.state = 499; this.match(esql_parser.MV_EXPAND); - this.state = 495; + this.state = 500; this.qualifiedName(); } } @@ -2684,30 +2724,30 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public commandOptions(): CommandOptionsContext { let localctx: CommandOptionsContext = new CommandOptionsContext(this, this._ctx, this.state); - this.enterRule(localctx, 86, esql_parser.RULE_commandOptions); + this.enterRule(localctx, 88, esql_parser.RULE_commandOptions); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 497; - this.commandOption(); this.state = 502; + this.commandOption(); + this.state = 507; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 47, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 498; + this.state = 503; this.match(esql_parser.COMMA); - this.state = 499; + this.state = 504; this.commandOption(); } } } - this.state = 504; + this.state = 509; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 47, this._ctx); } } } @@ -2728,15 +2768,15 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public commandOption(): CommandOptionContext { let localctx: CommandOptionContext = new CommandOptionContext(this, this._ctx, this.state); - this.enterRule(localctx, 88, esql_parser.RULE_commandOption); + this.enterRule(localctx, 90, esql_parser.RULE_commandOption); try { this.enterOuterAlt(localctx, 1); { - this.state = 505; + this.state = 510; this.identifier(); - this.state = 506; + this.state = 511; this.match(esql_parser.ASSIGN); - this.state = 507; + this.state = 512; this.constant(); } } @@ -2757,14 +2797,14 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public booleanValue(): BooleanValueContext { let localctx: BooleanValueContext = new BooleanValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 90, esql_parser.RULE_booleanValue); + this.enterRule(localctx, 92, esql_parser.RULE_booleanValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 509; + this.state = 514; _la = this._input.LA(1); - if(!(_la===38 || _la===52)) { + if(!(_la===37 || _la===51)) { this._errHandler.recoverInline(this); } else { @@ -2790,22 +2830,22 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public numericValue(): NumericValueContext { let localctx: NumericValueContext = new NumericValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 92, esql_parser.RULE_numericValue); + this.enterRule(localctx, 94, esql_parser.RULE_numericValue); try { - this.state = 513; + this.state = 518; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 46, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 48, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 511; + this.state = 516; this.decimalValue(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 512; + this.state = 517; this.integerValue(); } break; @@ -2828,19 +2868,19 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public decimalValue(): DecimalValueContext { let localctx: DecimalValueContext = new DecimalValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 94, esql_parser.RULE_decimalValue); + this.enterRule(localctx, 96, esql_parser.RULE_decimalValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 516; + this.state = 521; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===60 || _la===61) { + if (_la===59 || _la===60) { { - this.state = 515; + this.state = 520; _la = this._input.LA(1); - if(!(_la===60 || _la===61)) { + if(!(_la===59 || _la===60)) { this._errHandler.recoverInline(this); } else { @@ -2850,7 +2890,7 @@ export default class esql_parser extends parser_config { } } - this.state = 518; + this.state = 523; this.match(esql_parser.DECIMAL_LITERAL); } } @@ -2871,19 +2911,19 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public integerValue(): IntegerValueContext { let localctx: IntegerValueContext = new IntegerValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 96, esql_parser.RULE_integerValue); + this.enterRule(localctx, 98, esql_parser.RULE_integerValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 521; + this.state = 526; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===60 || _la===61) { + if (_la===59 || _la===60) { { - this.state = 520; + this.state = 525; _la = this._input.LA(1); - if(!(_la===60 || _la===61)) { + if(!(_la===59 || _la===60)) { this._errHandler.recoverInline(this); } else { @@ -2893,7 +2933,7 @@ export default class esql_parser extends parser_config { } } - this.state = 523; + this.state = 528; this.match(esql_parser.INTEGER_LITERAL); } } @@ -2914,11 +2954,11 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public string_(): StringContext { let localctx: StringContext = new StringContext(this, this._ctx, this.state); - this.enterRule(localctx, 98, esql_parser.RULE_string); + this.enterRule(localctx, 100, esql_parser.RULE_string); try { this.enterOuterAlt(localctx, 1); { - this.state = 525; + this.state = 530; this.match(esql_parser.QUOTED_STRING); } } @@ -2939,14 +2979,14 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public comparisonOperator(): ComparisonOperatorContext { let localctx: ComparisonOperatorContext = new ComparisonOperatorContext(this, this._ctx, this.state); - this.enterRule(localctx, 100, esql_parser.RULE_comparisonOperator); + this.enterRule(localctx, 102, esql_parser.RULE_comparisonOperator); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 527; + this.state = 532; _la = this._input.LA(1); - if(!(((((_la - 53)) & ~0x1F) === 0 && ((1 << (_la - 53)) & 125) !== 0))) { + if(!(((((_la - 52)) & ~0x1F) === 0 && ((1 << (_la - 52)) & 125) !== 0))) { this._errHandler.recoverInline(this); } else { @@ -2972,13 +3012,13 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public explainCommand(): ExplainCommandContext { let localctx: ExplainCommandContext = new ExplainCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 102, esql_parser.RULE_explainCommand); + this.enterRule(localctx, 104, esql_parser.RULE_explainCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 529; + this.state = 534; this.match(esql_parser.EXPLAIN); - this.state = 530; + this.state = 535; this.subqueryExpression(); } } @@ -2999,15 +3039,15 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public subqueryExpression(): SubqueryExpressionContext { let localctx: SubqueryExpressionContext = new SubqueryExpressionContext(this, this._ctx, this.state); - this.enterRule(localctx, 104, esql_parser.RULE_subqueryExpression); + this.enterRule(localctx, 106, esql_parser.RULE_subqueryExpression); try { this.enterOuterAlt(localctx, 1); { - this.state = 532; + this.state = 537; this.match(esql_parser.OPENING_BRACKET); - this.state = 533; + this.state = 538; this.query(0); - this.state = 534; + this.state = 539; this.match(esql_parser.CLOSING_BRACKET); } } @@ -3028,14 +3068,14 @@ export default class esql_parser extends parser_config { // @RuleVersion(0) public showCommand(): ShowCommandContext { let localctx: ShowCommandContext = new ShowCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 106, esql_parser.RULE_showCommand); + this.enterRule(localctx, 108, esql_parser.RULE_showCommand); try { localctx = new ShowInfoContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 536; + this.state = 541; this.match(esql_parser.SHOW); - this.state = 537; + this.state = 542; this.match(esql_parser.INFO); } } @@ -3054,34 +3094,6 @@ export default class esql_parser extends parser_config { return localctx; } // @RuleVersion(0) - public metaCommand(): MetaCommandContext { - let localctx: MetaCommandContext = new MetaCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 108, esql_parser.RULE_metaCommand); - try { - localctx = new MetaFunctionsContext(this, localctx); - this.enterOuterAlt(localctx, 1); - { - this.state = 539; - this.match(esql_parser.META); - this.state = 540; - this.match(esql_parser.FUNCTIONS); - } - } - catch (re) { - if (re instanceof RecognitionException) { - localctx.exception = re; - this._errHandler.reportError(this, re); - this._errHandler.recover(this, re); - } else { - throw re; - } - } - finally { - this.exitRule(); - } - return localctx; - } - // @RuleVersion(0) public enrichCommand(): EnrichCommandContext { let localctx: EnrichCommandContext = new EnrichCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 110, esql_parser.RULE_enrichCommand); @@ -3089,48 +3101,48 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 542; + this.state = 544; this.match(esql_parser.ENRICH); - this.state = 543; + this.state = 545; localctx._policyName = this.match(esql_parser.ENRICH_POLICY_NAME); - this.state = 546; + this.state = 548; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 49, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 51, this._ctx) ) { case 1: { - this.state = 544; + this.state = 546; this.match(esql_parser.ON); - this.state = 545; + this.state = 547; localctx._matchField = this.qualifiedNamePattern(); } break; } - this.state = 557; + this.state = 559; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 51, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 53, this._ctx) ) { case 1: { - this.state = 548; + this.state = 550; this.match(esql_parser.WITH); - this.state = 549; + this.state = 551; this.enrichWithClause(); - this.state = 554; + this.state = 556; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 50, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 52, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 550; + this.state = 552; this.match(esql_parser.COMMA); - this.state = 551; + this.state = 553; this.enrichWithClause(); } } } - this.state = 556; + this.state = 558; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 50, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 52, this._ctx); } } break; @@ -3158,19 +3170,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 562; + this.state = 564; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 52, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 54, this._ctx) ) { case 1: { - this.state = 559; + this.state = 561; localctx._newName = this.qualifiedNamePattern(); - this.state = 560; + this.state = 562; this.match(esql_parser.ASSIGN); } break; } - this.state = 564; + this.state = 566; localctx._enrichField = this.qualifiedNamePattern(); } } @@ -3195,13 +3207,13 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 566; + this.state = 568; this.match(esql_parser.DEV_LOOKUP); - this.state = 567; + this.state = 569; localctx._tableName = this.indexPattern(); - this.state = 568; + this.state = 570; this.match(esql_parser.ON); - this.state = 569; + this.state = 571; localctx._matchFields = this.qualifiedNamePatterns(); } } @@ -3226,18 +3238,18 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 571; + this.state = 573; this.match(esql_parser.DEV_INLINESTATS); - this.state = 572; + this.state = 574; localctx._stats = this.fields(); - this.state = 575; + this.state = 577; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 53, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 55, this._ctx) ) { case 1: { - this.state = 573; + this.state = 575; this.match(esql_parser.BY); - this.state = 574; + this.state = 576; localctx._grouping = this.fields(); } break; @@ -3327,7 +3339,7 @@ export default class esql_parser extends parser_config { return true; } - public static readonly _serializedATN: number[] = [4,1,125,578,2,0,7,0, + public static readonly _serializedATN: number[] = [4,1,120,580,2,0,7,0, 2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9, 2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2, 17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24, @@ -3337,184 +3349,186 @@ export default class esql_parser extends parser_config { 46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,52,2,53, 7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,1,0,1,0,1,0,1,1, 1,1,1,1,1,1,1,1,1,1,5,1,128,8,1,10,1,12,1,131,9,1,1,2,1,2,1,2,1,2,1,2,1, - 2,1,2,3,2,140,8,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1, - 3,1,3,1,3,3,3,158,8,3,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,3,5,170,8, - 5,1,5,1,5,1,5,1,5,1,5,5,5,177,8,5,10,5,12,5,180,9,5,1,5,1,5,1,5,1,5,1,5, - 3,5,187,8,5,1,5,1,5,1,5,1,5,3,5,193,8,5,1,5,1,5,1,5,1,5,1,5,1,5,5,5,201, - 8,5,10,5,12,5,204,9,5,1,6,1,6,3,6,208,8,6,1,6,1,6,1,6,1,6,1,6,3,6,215,8, - 6,1,6,1,6,1,6,3,6,220,8,6,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,3,8,231,8, - 8,1,9,1,9,1,9,1,9,3,9,237,8,9,1,9,1,9,1,9,1,9,1,9,1,9,5,9,245,8,9,10,9, - 12,9,248,9,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,3,10,258,8,10,1,10, - 1,10,1,10,5,10,263,8,10,10,10,12,10,266,9,10,1,11,1,11,1,11,1,11,1,11,1, - 11,5,11,274,8,11,10,11,12,11,277,9,11,3,11,279,8,11,1,11,1,11,1,12,1,12, - 1,13,1,13,1,13,1,14,1,14,1,14,5,14,291,8,14,10,14,12,14,294,9,14,1,15,1, - 15,1,15,1,15,1,15,3,15,301,8,15,1,16,1,16,1,16,1,16,5,16,307,8,16,10,16, - 12,16,310,9,16,1,16,3,16,313,8,16,1,17,1,17,1,17,1,17,1,17,3,17,320,8,17, - 1,18,1,18,1,19,1,19,1,20,1,20,3,20,328,8,20,1,21,1,21,1,21,1,21,5,21,334, - 8,21,10,21,12,21,337,9,21,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,5,23, - 347,8,23,10,23,12,23,350,9,23,1,23,3,23,353,8,23,1,23,1,23,3,23,357,8,23, - 1,24,1,24,1,24,1,25,1,25,3,25,364,8,25,1,25,1,25,3,25,368,8,25,1,26,1,26, - 1,26,5,26,373,8,26,10,26,12,26,376,9,26,1,27,1,27,1,27,5,27,381,8,27,10, - 27,12,27,384,9,27,1,28,1,28,1,28,5,28,389,8,28,10,28,12,28,392,9,28,1,29, - 1,29,1,30,1,30,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1, - 31,1,31,5,31,411,8,31,10,31,12,31,414,9,31,1,31,1,31,1,31,1,31,1,31,1,31, - 5,31,422,8,31,10,31,12,31,425,9,31,1,31,1,31,1,31,1,31,1,31,1,31,5,31,433, - 8,31,10,31,12,31,436,9,31,1,31,1,31,3,31,440,8,31,1,32,1,32,3,32,444,8, - 32,1,33,1,33,1,33,1,34,1,34,1,34,1,34,5,34,453,8,34,10,34,12,34,456,9,34, - 1,35,1,35,3,35,460,8,35,1,35,1,35,3,35,464,8,35,1,36,1,36,1,36,1,37,1,37, - 1,37,1,38,1,38,1,38,1,38,5,38,476,8,38,10,38,12,38,479,9,38,1,39,1,39,1, - 39,1,39,1,40,1,40,1,40,1,40,3,40,489,8,40,1,41,1,41,1,41,1,41,1,42,1,42, - 1,42,1,43,1,43,1,43,5,43,501,8,43,10,43,12,43,504,9,43,1,44,1,44,1,44,1, - 44,1,45,1,45,1,46,1,46,3,46,514,8,46,1,47,3,47,517,8,47,1,47,1,47,1,48, - 3,48,522,8,48,1,48,1,48,1,49,1,49,1,50,1,50,1,51,1,51,1,51,1,52,1,52,1, - 52,1,52,1,53,1,53,1,53,1,54,1,54,1,54,1,55,1,55,1,55,1,55,3,55,547,8,55, - 1,55,1,55,1,55,1,55,5,55,553,8,55,10,55,12,55,556,9,55,3,55,558,8,55,1, - 56,1,56,1,56,3,56,563,8,56,1,56,1,56,1,57,1,57,1,57,1,57,1,57,1,58,1,58, - 1,58,1,58,3,58,576,8,58,1,58,0,4,2,10,18,20,59,0,2,4,6,8,10,12,14,16,18, - 20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66, - 68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110, - 112,114,116,0,8,1,0,60,61,1,0,62,64,2,0,27,27,77,77,1,0,68,69,2,0,32,32, - 36,36,2,0,39,39,42,42,2,0,38,38,52,52,2,0,53,53,55,59,603,0,118,1,0,0,0, - 2,121,1,0,0,0,4,139,1,0,0,0,6,157,1,0,0,0,8,159,1,0,0,0,10,192,1,0,0,0, - 12,219,1,0,0,0,14,221,1,0,0,0,16,230,1,0,0,0,18,236,1,0,0,0,20,257,1,0, - 0,0,22,267,1,0,0,0,24,282,1,0,0,0,26,284,1,0,0,0,28,287,1,0,0,0,30,300, - 1,0,0,0,32,302,1,0,0,0,34,319,1,0,0,0,36,321,1,0,0,0,38,323,1,0,0,0,40, - 327,1,0,0,0,42,329,1,0,0,0,44,338,1,0,0,0,46,342,1,0,0,0,48,358,1,0,0,0, - 50,361,1,0,0,0,52,369,1,0,0,0,54,377,1,0,0,0,56,385,1,0,0,0,58,393,1,0, - 0,0,60,395,1,0,0,0,62,439,1,0,0,0,64,443,1,0,0,0,66,445,1,0,0,0,68,448, - 1,0,0,0,70,457,1,0,0,0,72,465,1,0,0,0,74,468,1,0,0,0,76,471,1,0,0,0,78, - 480,1,0,0,0,80,484,1,0,0,0,82,490,1,0,0,0,84,494,1,0,0,0,86,497,1,0,0,0, - 88,505,1,0,0,0,90,509,1,0,0,0,92,513,1,0,0,0,94,516,1,0,0,0,96,521,1,0, - 0,0,98,525,1,0,0,0,100,527,1,0,0,0,102,529,1,0,0,0,104,532,1,0,0,0,106, - 536,1,0,0,0,108,539,1,0,0,0,110,542,1,0,0,0,112,562,1,0,0,0,114,566,1,0, - 0,0,116,571,1,0,0,0,118,119,3,2,1,0,119,120,5,0,0,1,120,1,1,0,0,0,121,122, - 6,1,-1,0,122,123,3,4,2,0,123,129,1,0,0,0,124,125,10,1,0,0,125,126,5,26, - 0,0,126,128,3,6,3,0,127,124,1,0,0,0,128,131,1,0,0,0,129,127,1,0,0,0,129, - 130,1,0,0,0,130,3,1,0,0,0,131,129,1,0,0,0,132,140,3,102,51,0,133,140,3, - 32,16,0,134,140,3,108,54,0,135,140,3,26,13,0,136,140,3,106,53,0,137,138, - 4,2,1,0,138,140,3,46,23,0,139,132,1,0,0,0,139,133,1,0,0,0,139,134,1,0,0, - 0,139,135,1,0,0,0,139,136,1,0,0,0,139,137,1,0,0,0,140,5,1,0,0,0,141,158, - 3,48,24,0,142,158,3,8,4,0,143,158,3,72,36,0,144,158,3,66,33,0,145,158,3, - 50,25,0,146,158,3,68,34,0,147,158,3,74,37,0,148,158,3,76,38,0,149,158,3, - 80,40,0,150,158,3,82,41,0,151,158,3,110,55,0,152,158,3,84,42,0,153,154, - 4,3,2,0,154,158,3,116,58,0,155,156,4,3,3,0,156,158,3,114,57,0,157,141,1, - 0,0,0,157,142,1,0,0,0,157,143,1,0,0,0,157,144,1,0,0,0,157,145,1,0,0,0,157, - 146,1,0,0,0,157,147,1,0,0,0,157,148,1,0,0,0,157,149,1,0,0,0,157,150,1,0, - 0,0,157,151,1,0,0,0,157,152,1,0,0,0,157,153,1,0,0,0,157,155,1,0,0,0,158, - 7,1,0,0,0,159,160,5,17,0,0,160,161,3,10,5,0,161,9,1,0,0,0,162,163,6,5,-1, - 0,163,164,5,45,0,0,164,193,3,10,5,8,165,193,3,16,8,0,166,193,3,12,6,0,167, - 169,3,16,8,0,168,170,5,45,0,0,169,168,1,0,0,0,169,170,1,0,0,0,170,171,1, - 0,0,0,171,172,5,40,0,0,172,173,5,44,0,0,173,178,3,16,8,0,174,175,5,35,0, - 0,175,177,3,16,8,0,176,174,1,0,0,0,177,180,1,0,0,0,178,176,1,0,0,0,178, - 179,1,0,0,0,179,181,1,0,0,0,180,178,1,0,0,0,181,182,5,51,0,0,182,193,1, - 0,0,0,183,184,3,16,8,0,184,186,5,41,0,0,185,187,5,45,0,0,186,185,1,0,0, - 0,186,187,1,0,0,0,187,188,1,0,0,0,188,189,5,46,0,0,189,193,1,0,0,0,190, - 191,4,5,4,0,191,193,3,14,7,0,192,162,1,0,0,0,192,165,1,0,0,0,192,166,1, - 0,0,0,192,167,1,0,0,0,192,183,1,0,0,0,192,190,1,0,0,0,193,202,1,0,0,0,194, - 195,10,5,0,0,195,196,5,31,0,0,196,201,3,10,5,6,197,198,10,4,0,0,198,199, - 5,48,0,0,199,201,3,10,5,5,200,194,1,0,0,0,200,197,1,0,0,0,201,204,1,0,0, - 0,202,200,1,0,0,0,202,203,1,0,0,0,203,11,1,0,0,0,204,202,1,0,0,0,205,207, - 3,16,8,0,206,208,5,45,0,0,207,206,1,0,0,0,207,208,1,0,0,0,208,209,1,0,0, - 0,209,210,5,43,0,0,210,211,3,98,49,0,211,220,1,0,0,0,212,214,3,16,8,0,213, - 215,5,45,0,0,214,213,1,0,0,0,214,215,1,0,0,0,215,216,1,0,0,0,216,217,5, - 50,0,0,217,218,3,98,49,0,218,220,1,0,0,0,219,205,1,0,0,0,219,212,1,0,0, - 0,220,13,1,0,0,0,221,222,3,16,8,0,222,223,5,20,0,0,223,224,3,98,49,0,224, - 15,1,0,0,0,225,231,3,18,9,0,226,227,3,18,9,0,227,228,3,100,50,0,228,229, - 3,18,9,0,229,231,1,0,0,0,230,225,1,0,0,0,230,226,1,0,0,0,231,17,1,0,0,0, - 232,233,6,9,-1,0,233,237,3,20,10,0,234,235,7,0,0,0,235,237,3,18,9,3,236, - 232,1,0,0,0,236,234,1,0,0,0,237,246,1,0,0,0,238,239,10,2,0,0,239,240,7, - 1,0,0,240,245,3,18,9,3,241,242,10,1,0,0,242,243,7,0,0,0,243,245,3,18,9, - 2,244,238,1,0,0,0,244,241,1,0,0,0,245,248,1,0,0,0,246,244,1,0,0,0,246,247, - 1,0,0,0,247,19,1,0,0,0,248,246,1,0,0,0,249,250,6,10,-1,0,250,258,3,62,31, - 0,251,258,3,52,26,0,252,258,3,22,11,0,253,254,5,44,0,0,254,255,3,10,5,0, - 255,256,5,51,0,0,256,258,1,0,0,0,257,249,1,0,0,0,257,251,1,0,0,0,257,252, - 1,0,0,0,257,253,1,0,0,0,258,264,1,0,0,0,259,260,10,1,0,0,260,261,5,34,0, - 0,261,263,3,24,12,0,262,259,1,0,0,0,263,266,1,0,0,0,264,262,1,0,0,0,264, - 265,1,0,0,0,265,21,1,0,0,0,266,264,1,0,0,0,267,268,3,58,29,0,268,278,5, - 44,0,0,269,279,5,62,0,0,270,275,3,10,5,0,271,272,5,35,0,0,272,274,3,10, - 5,0,273,271,1,0,0,0,274,277,1,0,0,0,275,273,1,0,0,0,275,276,1,0,0,0,276, - 279,1,0,0,0,277,275,1,0,0,0,278,269,1,0,0,0,278,270,1,0,0,0,278,279,1,0, - 0,0,279,280,1,0,0,0,280,281,5,51,0,0,281,23,1,0,0,0,282,283,3,58,29,0,283, - 25,1,0,0,0,284,285,5,13,0,0,285,286,3,28,14,0,286,27,1,0,0,0,287,292,3, - 30,15,0,288,289,5,35,0,0,289,291,3,30,15,0,290,288,1,0,0,0,291,294,1,0, - 0,0,292,290,1,0,0,0,292,293,1,0,0,0,293,29,1,0,0,0,294,292,1,0,0,0,295, - 301,3,10,5,0,296,297,3,52,26,0,297,298,5,33,0,0,298,299,3,10,5,0,299,301, - 1,0,0,0,300,295,1,0,0,0,300,296,1,0,0,0,301,31,1,0,0,0,302,303,5,6,0,0, - 303,308,3,34,17,0,304,305,5,35,0,0,305,307,3,34,17,0,306,304,1,0,0,0,307, - 310,1,0,0,0,308,306,1,0,0,0,308,309,1,0,0,0,309,312,1,0,0,0,310,308,1,0, - 0,0,311,313,3,40,20,0,312,311,1,0,0,0,312,313,1,0,0,0,313,33,1,0,0,0,314, - 315,3,36,18,0,315,316,5,109,0,0,316,317,3,38,19,0,317,320,1,0,0,0,318,320, - 3,38,19,0,319,314,1,0,0,0,319,318,1,0,0,0,320,35,1,0,0,0,321,322,5,77,0, - 0,322,37,1,0,0,0,323,324,7,2,0,0,324,39,1,0,0,0,325,328,3,42,21,0,326,328, - 3,44,22,0,327,325,1,0,0,0,327,326,1,0,0,0,328,41,1,0,0,0,329,330,5,76,0, - 0,330,335,5,77,0,0,331,332,5,35,0,0,332,334,5,77,0,0,333,331,1,0,0,0,334, - 337,1,0,0,0,335,333,1,0,0,0,335,336,1,0,0,0,336,43,1,0,0,0,337,335,1,0, - 0,0,338,339,5,66,0,0,339,340,3,42,21,0,340,341,5,67,0,0,341,45,1,0,0,0, - 342,343,5,21,0,0,343,348,3,34,17,0,344,345,5,35,0,0,345,347,3,34,17,0,346, - 344,1,0,0,0,347,350,1,0,0,0,348,346,1,0,0,0,348,349,1,0,0,0,349,352,1,0, - 0,0,350,348,1,0,0,0,351,353,3,28,14,0,352,351,1,0,0,0,352,353,1,0,0,0,353, - 356,1,0,0,0,354,355,5,30,0,0,355,357,3,28,14,0,356,354,1,0,0,0,356,357, - 1,0,0,0,357,47,1,0,0,0,358,359,5,4,0,0,359,360,3,28,14,0,360,49,1,0,0,0, - 361,363,5,16,0,0,362,364,3,28,14,0,363,362,1,0,0,0,363,364,1,0,0,0,364, - 367,1,0,0,0,365,366,5,30,0,0,366,368,3,28,14,0,367,365,1,0,0,0,367,368, - 1,0,0,0,368,51,1,0,0,0,369,374,3,58,29,0,370,371,5,37,0,0,371,373,3,58, - 29,0,372,370,1,0,0,0,373,376,1,0,0,0,374,372,1,0,0,0,374,375,1,0,0,0,375, - 53,1,0,0,0,376,374,1,0,0,0,377,382,3,60,30,0,378,379,5,37,0,0,379,381,3, - 60,30,0,380,378,1,0,0,0,381,384,1,0,0,0,382,380,1,0,0,0,382,383,1,0,0,0, - 383,55,1,0,0,0,384,382,1,0,0,0,385,390,3,54,27,0,386,387,5,35,0,0,387,389, - 3,54,27,0,388,386,1,0,0,0,389,392,1,0,0,0,390,388,1,0,0,0,390,391,1,0,0, - 0,391,57,1,0,0,0,392,390,1,0,0,0,393,394,7,3,0,0,394,59,1,0,0,0,395,396, - 5,81,0,0,396,61,1,0,0,0,397,440,5,46,0,0,398,399,3,96,48,0,399,400,5,68, - 0,0,400,440,1,0,0,0,401,440,3,94,47,0,402,440,3,96,48,0,403,440,3,90,45, - 0,404,440,3,64,32,0,405,440,3,98,49,0,406,407,5,66,0,0,407,412,3,92,46, - 0,408,409,5,35,0,0,409,411,3,92,46,0,410,408,1,0,0,0,411,414,1,0,0,0,412, - 410,1,0,0,0,412,413,1,0,0,0,413,415,1,0,0,0,414,412,1,0,0,0,415,416,5,67, - 0,0,416,440,1,0,0,0,417,418,5,66,0,0,418,423,3,90,45,0,419,420,5,35,0,0, - 420,422,3,90,45,0,421,419,1,0,0,0,422,425,1,0,0,0,423,421,1,0,0,0,423,424, - 1,0,0,0,424,426,1,0,0,0,425,423,1,0,0,0,426,427,5,67,0,0,427,440,1,0,0, - 0,428,429,5,66,0,0,429,434,3,98,49,0,430,431,5,35,0,0,431,433,3,98,49,0, - 432,430,1,0,0,0,433,436,1,0,0,0,434,432,1,0,0,0,434,435,1,0,0,0,435,437, - 1,0,0,0,436,434,1,0,0,0,437,438,5,67,0,0,438,440,1,0,0,0,439,397,1,0,0, - 0,439,398,1,0,0,0,439,401,1,0,0,0,439,402,1,0,0,0,439,403,1,0,0,0,439,404, - 1,0,0,0,439,405,1,0,0,0,439,406,1,0,0,0,439,417,1,0,0,0,439,428,1,0,0,0, - 440,63,1,0,0,0,441,444,5,49,0,0,442,444,5,65,0,0,443,441,1,0,0,0,443,442, - 1,0,0,0,444,65,1,0,0,0,445,446,5,9,0,0,446,447,5,28,0,0,447,67,1,0,0,0, - 448,449,5,15,0,0,449,454,3,70,35,0,450,451,5,35,0,0,451,453,3,70,35,0,452, - 450,1,0,0,0,453,456,1,0,0,0,454,452,1,0,0,0,454,455,1,0,0,0,455,69,1,0, - 0,0,456,454,1,0,0,0,457,459,3,10,5,0,458,460,7,4,0,0,459,458,1,0,0,0,459, - 460,1,0,0,0,460,463,1,0,0,0,461,462,5,47,0,0,462,464,7,5,0,0,463,461,1, - 0,0,0,463,464,1,0,0,0,464,71,1,0,0,0,465,466,5,8,0,0,466,467,3,56,28,0, - 467,73,1,0,0,0,468,469,5,2,0,0,469,470,3,56,28,0,470,75,1,0,0,0,471,472, - 5,12,0,0,472,477,3,78,39,0,473,474,5,35,0,0,474,476,3,78,39,0,475,473,1, - 0,0,0,476,479,1,0,0,0,477,475,1,0,0,0,477,478,1,0,0,0,478,77,1,0,0,0,479, - 477,1,0,0,0,480,481,3,54,27,0,481,482,5,85,0,0,482,483,3,54,27,0,483,79, - 1,0,0,0,484,485,5,1,0,0,485,486,3,20,10,0,486,488,3,98,49,0,487,489,3,86, - 43,0,488,487,1,0,0,0,488,489,1,0,0,0,489,81,1,0,0,0,490,491,5,7,0,0,491, - 492,3,20,10,0,492,493,3,98,49,0,493,83,1,0,0,0,494,495,5,11,0,0,495,496, - 3,52,26,0,496,85,1,0,0,0,497,502,3,88,44,0,498,499,5,35,0,0,499,501,3,88, - 44,0,500,498,1,0,0,0,501,504,1,0,0,0,502,500,1,0,0,0,502,503,1,0,0,0,503, - 87,1,0,0,0,504,502,1,0,0,0,505,506,3,58,29,0,506,507,5,33,0,0,507,508,3, - 62,31,0,508,89,1,0,0,0,509,510,7,6,0,0,510,91,1,0,0,0,511,514,3,94,47,0, - 512,514,3,96,48,0,513,511,1,0,0,0,513,512,1,0,0,0,514,93,1,0,0,0,515,517, - 7,0,0,0,516,515,1,0,0,0,516,517,1,0,0,0,517,518,1,0,0,0,518,519,5,29,0, - 0,519,95,1,0,0,0,520,522,7,0,0,0,521,520,1,0,0,0,521,522,1,0,0,0,522,523, - 1,0,0,0,523,524,5,28,0,0,524,97,1,0,0,0,525,526,5,27,0,0,526,99,1,0,0,0, - 527,528,7,7,0,0,528,101,1,0,0,0,529,530,5,5,0,0,530,531,3,104,52,0,531, - 103,1,0,0,0,532,533,5,66,0,0,533,534,3,2,1,0,534,535,5,67,0,0,535,105,1, - 0,0,0,536,537,5,14,0,0,537,538,5,101,0,0,538,107,1,0,0,0,539,540,5,10,0, - 0,540,541,5,105,0,0,541,109,1,0,0,0,542,543,5,3,0,0,543,546,5,91,0,0,544, - 545,5,89,0,0,545,547,3,54,27,0,546,544,1,0,0,0,546,547,1,0,0,0,547,557, - 1,0,0,0,548,549,5,90,0,0,549,554,3,112,56,0,550,551,5,35,0,0,551,553,3, - 112,56,0,552,550,1,0,0,0,553,556,1,0,0,0,554,552,1,0,0,0,554,555,1,0,0, - 0,555,558,1,0,0,0,556,554,1,0,0,0,557,548,1,0,0,0,557,558,1,0,0,0,558,111, - 1,0,0,0,559,560,3,54,27,0,560,561,5,33,0,0,561,563,1,0,0,0,562,559,1,0, - 0,0,562,563,1,0,0,0,563,564,1,0,0,0,564,565,3,54,27,0,565,113,1,0,0,0,566, - 567,5,19,0,0,567,568,3,34,17,0,568,569,5,89,0,0,569,570,3,56,28,0,570,115, - 1,0,0,0,571,572,5,18,0,0,572,575,3,28,14,0,573,574,5,30,0,0,574,576,3,28, - 14,0,575,573,1,0,0,0,575,576,1,0,0,0,576,117,1,0,0,0,54,129,139,157,169, - 178,186,192,200,202,207,214,219,230,236,244,246,257,264,275,278,292,300, - 308,312,319,327,335,348,352,356,363,367,374,382,390,412,423,434,439,443, - 454,459,463,477,488,502,513,516,521,546,554,557,562,575]; + 2,3,2,139,8,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1, + 3,1,3,3,3,157,8,3,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,3,5,169,8,5,1, + 5,1,5,1,5,1,5,1,5,5,5,176,8,5,10,5,12,5,179,9,5,1,5,1,5,1,5,1,5,1,5,3,5, + 186,8,5,1,5,1,5,1,5,1,5,3,5,192,8,5,1,5,1,5,1,5,1,5,1,5,1,5,5,5,200,8,5, + 10,5,12,5,203,9,5,1,6,1,6,3,6,207,8,6,1,6,1,6,1,6,1,6,1,6,3,6,214,8,6,1, + 6,1,6,1,6,3,6,219,8,6,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,3,8,230,8,8,1, + 9,1,9,1,9,1,9,3,9,236,8,9,1,9,1,9,1,9,1,9,1,9,1,9,5,9,244,8,9,10,9,12,9, + 247,9,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,3,10,257,8,10,1,10,1,10, + 1,10,5,10,262,8,10,10,10,12,10,265,9,10,1,11,1,11,1,11,1,11,1,11,1,11,5, + 11,273,8,11,10,11,12,11,276,9,11,3,11,278,8,11,1,11,1,11,1,12,1,12,1,13, + 1,13,1,13,1,14,1,14,1,14,5,14,290,8,14,10,14,12,14,293,9,14,1,15,1,15,1, + 15,1,15,1,15,3,15,300,8,15,1,16,1,16,1,16,1,16,5,16,306,8,16,10,16,12,16, + 309,9,16,1,16,3,16,312,8,16,1,17,1,17,1,17,1,17,1,17,3,17,319,8,17,1,18, + 1,18,1,19,1,19,1,20,1,20,3,20,327,8,20,1,21,1,21,1,21,1,21,5,21,333,8,21, + 10,21,12,21,336,9,21,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,5,23,346,8, + 23,10,23,12,23,349,9,23,1,23,3,23,352,8,23,1,23,1,23,3,23,356,8,23,1,24, + 1,24,1,24,1,25,1,25,3,25,363,8,25,1,25,1,25,3,25,367,8,25,1,26,1,26,1,26, + 5,26,372,8,26,10,26,12,26,375,9,26,1,27,1,27,1,27,5,27,380,8,27,10,27,12, + 27,383,9,27,1,28,1,28,1,28,5,28,388,8,28,10,28,12,28,391,9,28,1,29,1,29, + 1,30,1,30,3,30,397,8,30,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1, + 31,1,31,1,31,1,31,5,31,412,8,31,10,31,12,31,415,9,31,1,31,1,31,1,31,1,31, + 1,31,1,31,5,31,423,8,31,10,31,12,31,426,9,31,1,31,1,31,1,31,1,31,1,31,1, + 31,5,31,434,8,31,10,31,12,31,437,9,31,1,31,1,31,3,31,441,8,31,1,32,1,32, + 3,32,445,8,32,1,33,1,33,3,33,449,8,33,1,34,1,34,1,34,1,35,1,35,1,35,1,35, + 5,35,458,8,35,10,35,12,35,461,9,35,1,36,1,36,3,36,465,8,36,1,36,1,36,3, + 36,469,8,36,1,37,1,37,1,37,1,38,1,38,1,38,1,39,1,39,1,39,1,39,5,39,481, + 8,39,10,39,12,39,484,9,39,1,40,1,40,1,40,1,40,1,41,1,41,1,41,1,41,3,41, + 494,8,41,1,42,1,42,1,42,1,42,1,43,1,43,1,43,1,44,1,44,1,44,5,44,506,8,44, + 10,44,12,44,509,9,44,1,45,1,45,1,45,1,45,1,46,1,46,1,47,1,47,3,47,519,8, + 47,1,48,3,48,522,8,48,1,48,1,48,1,49,3,49,527,8,49,1,49,1,49,1,50,1,50, + 1,51,1,51,1,52,1,52,1,52,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,55,1,55,1, + 55,1,55,3,55,549,8,55,1,55,1,55,1,55,1,55,5,55,555,8,55,10,55,12,55,558, + 9,55,3,55,560,8,55,1,56,1,56,1,56,3,56,565,8,56,1,56,1,56,1,57,1,57,1,57, + 1,57,1,57,1,58,1,58,1,58,1,58,3,58,578,8,58,1,58,0,4,2,10,18,20,59,0,2, + 4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52, + 54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100, + 102,104,106,108,110,112,114,116,0,8,1,0,59,60,1,0,61,63,2,0,26,26,76,76, + 1,0,67,68,2,0,31,31,35,35,2,0,38,38,41,41,2,0,37,37,51,51,2,0,52,52,54, + 58,606,0,118,1,0,0,0,2,121,1,0,0,0,4,138,1,0,0,0,6,156,1,0,0,0,8,158,1, + 0,0,0,10,191,1,0,0,0,12,218,1,0,0,0,14,220,1,0,0,0,16,229,1,0,0,0,18,235, + 1,0,0,0,20,256,1,0,0,0,22,266,1,0,0,0,24,281,1,0,0,0,26,283,1,0,0,0,28, + 286,1,0,0,0,30,299,1,0,0,0,32,301,1,0,0,0,34,318,1,0,0,0,36,320,1,0,0,0, + 38,322,1,0,0,0,40,326,1,0,0,0,42,328,1,0,0,0,44,337,1,0,0,0,46,341,1,0, + 0,0,48,357,1,0,0,0,50,360,1,0,0,0,52,368,1,0,0,0,54,376,1,0,0,0,56,384, + 1,0,0,0,58,392,1,0,0,0,60,396,1,0,0,0,62,440,1,0,0,0,64,444,1,0,0,0,66, + 448,1,0,0,0,68,450,1,0,0,0,70,453,1,0,0,0,72,462,1,0,0,0,74,470,1,0,0,0, + 76,473,1,0,0,0,78,476,1,0,0,0,80,485,1,0,0,0,82,489,1,0,0,0,84,495,1,0, + 0,0,86,499,1,0,0,0,88,502,1,0,0,0,90,510,1,0,0,0,92,514,1,0,0,0,94,518, + 1,0,0,0,96,521,1,0,0,0,98,526,1,0,0,0,100,530,1,0,0,0,102,532,1,0,0,0,104, + 534,1,0,0,0,106,537,1,0,0,0,108,541,1,0,0,0,110,544,1,0,0,0,112,564,1,0, + 0,0,114,568,1,0,0,0,116,573,1,0,0,0,118,119,3,2,1,0,119,120,5,0,0,1,120, + 1,1,0,0,0,121,122,6,1,-1,0,122,123,3,4,2,0,123,129,1,0,0,0,124,125,10,1, + 0,0,125,126,5,25,0,0,126,128,3,6,3,0,127,124,1,0,0,0,128,131,1,0,0,0,129, + 127,1,0,0,0,129,130,1,0,0,0,130,3,1,0,0,0,131,129,1,0,0,0,132,139,3,104, + 52,0,133,139,3,32,16,0,134,139,3,26,13,0,135,139,3,108,54,0,136,137,4,2, + 1,0,137,139,3,46,23,0,138,132,1,0,0,0,138,133,1,0,0,0,138,134,1,0,0,0,138, + 135,1,0,0,0,138,136,1,0,0,0,139,5,1,0,0,0,140,157,3,48,24,0,141,157,3,8, + 4,0,142,157,3,74,37,0,143,157,3,68,34,0,144,157,3,50,25,0,145,157,3,70, + 35,0,146,157,3,76,38,0,147,157,3,78,39,0,148,157,3,82,41,0,149,157,3,84, + 42,0,150,157,3,110,55,0,151,157,3,86,43,0,152,153,4,3,2,0,153,157,3,116, + 58,0,154,155,4,3,3,0,155,157,3,114,57,0,156,140,1,0,0,0,156,141,1,0,0,0, + 156,142,1,0,0,0,156,143,1,0,0,0,156,144,1,0,0,0,156,145,1,0,0,0,156,146, + 1,0,0,0,156,147,1,0,0,0,156,148,1,0,0,0,156,149,1,0,0,0,156,150,1,0,0,0, + 156,151,1,0,0,0,156,152,1,0,0,0,156,154,1,0,0,0,157,7,1,0,0,0,158,159,5, + 16,0,0,159,160,3,10,5,0,160,9,1,0,0,0,161,162,6,5,-1,0,162,163,5,44,0,0, + 163,192,3,10,5,8,164,192,3,16,8,0,165,192,3,12,6,0,166,168,3,16,8,0,167, + 169,5,44,0,0,168,167,1,0,0,0,168,169,1,0,0,0,169,170,1,0,0,0,170,171,5, + 39,0,0,171,172,5,43,0,0,172,177,3,16,8,0,173,174,5,34,0,0,174,176,3,16, + 8,0,175,173,1,0,0,0,176,179,1,0,0,0,177,175,1,0,0,0,177,178,1,0,0,0,178, + 180,1,0,0,0,179,177,1,0,0,0,180,181,5,50,0,0,181,192,1,0,0,0,182,183,3, + 16,8,0,183,185,5,40,0,0,184,186,5,44,0,0,185,184,1,0,0,0,185,186,1,0,0, + 0,186,187,1,0,0,0,187,188,5,45,0,0,188,192,1,0,0,0,189,190,4,5,4,0,190, + 192,3,14,7,0,191,161,1,0,0,0,191,164,1,0,0,0,191,165,1,0,0,0,191,166,1, + 0,0,0,191,182,1,0,0,0,191,189,1,0,0,0,192,201,1,0,0,0,193,194,10,5,0,0, + 194,195,5,30,0,0,195,200,3,10,5,6,196,197,10,4,0,0,197,198,5,47,0,0,198, + 200,3,10,5,5,199,193,1,0,0,0,199,196,1,0,0,0,200,203,1,0,0,0,201,199,1, + 0,0,0,201,202,1,0,0,0,202,11,1,0,0,0,203,201,1,0,0,0,204,206,3,16,8,0,205, + 207,5,44,0,0,206,205,1,0,0,0,206,207,1,0,0,0,207,208,1,0,0,0,208,209,5, + 42,0,0,209,210,3,100,50,0,210,219,1,0,0,0,211,213,3,16,8,0,212,214,5,44, + 0,0,213,212,1,0,0,0,213,214,1,0,0,0,214,215,1,0,0,0,215,216,5,49,0,0,216, + 217,3,100,50,0,217,219,1,0,0,0,218,204,1,0,0,0,218,211,1,0,0,0,219,13,1, + 0,0,0,220,221,3,16,8,0,221,222,5,19,0,0,222,223,3,100,50,0,223,15,1,0,0, + 0,224,230,3,18,9,0,225,226,3,18,9,0,226,227,3,102,51,0,227,228,3,18,9,0, + 228,230,1,0,0,0,229,224,1,0,0,0,229,225,1,0,0,0,230,17,1,0,0,0,231,232, + 6,9,-1,0,232,236,3,20,10,0,233,234,7,0,0,0,234,236,3,18,9,3,235,231,1,0, + 0,0,235,233,1,0,0,0,236,245,1,0,0,0,237,238,10,2,0,0,238,239,7,1,0,0,239, + 244,3,18,9,3,240,241,10,1,0,0,241,242,7,0,0,0,242,244,3,18,9,2,243,237, + 1,0,0,0,243,240,1,0,0,0,244,247,1,0,0,0,245,243,1,0,0,0,245,246,1,0,0,0, + 246,19,1,0,0,0,247,245,1,0,0,0,248,249,6,10,-1,0,249,257,3,62,31,0,250, + 257,3,52,26,0,251,257,3,22,11,0,252,253,5,43,0,0,253,254,3,10,5,0,254,255, + 5,50,0,0,255,257,1,0,0,0,256,248,1,0,0,0,256,250,1,0,0,0,256,251,1,0,0, + 0,256,252,1,0,0,0,257,263,1,0,0,0,258,259,10,1,0,0,259,260,5,33,0,0,260, + 262,3,24,12,0,261,258,1,0,0,0,262,265,1,0,0,0,263,261,1,0,0,0,263,264,1, + 0,0,0,264,21,1,0,0,0,265,263,1,0,0,0,266,267,3,66,33,0,267,277,5,43,0,0, + 268,278,5,61,0,0,269,274,3,10,5,0,270,271,5,34,0,0,271,273,3,10,5,0,272, + 270,1,0,0,0,273,276,1,0,0,0,274,272,1,0,0,0,274,275,1,0,0,0,275,278,1,0, + 0,0,276,274,1,0,0,0,277,268,1,0,0,0,277,269,1,0,0,0,277,278,1,0,0,0,278, + 279,1,0,0,0,279,280,5,50,0,0,280,23,1,0,0,0,281,282,3,58,29,0,282,25,1, + 0,0,0,283,284,5,12,0,0,284,285,3,28,14,0,285,27,1,0,0,0,286,291,3,30,15, + 0,287,288,5,34,0,0,288,290,3,30,15,0,289,287,1,0,0,0,290,293,1,0,0,0,291, + 289,1,0,0,0,291,292,1,0,0,0,292,29,1,0,0,0,293,291,1,0,0,0,294,300,3,10, + 5,0,295,296,3,52,26,0,296,297,5,32,0,0,297,298,3,10,5,0,298,300,1,0,0,0, + 299,294,1,0,0,0,299,295,1,0,0,0,300,31,1,0,0,0,301,302,5,6,0,0,302,307, + 3,34,17,0,303,304,5,34,0,0,304,306,3,34,17,0,305,303,1,0,0,0,306,309,1, + 0,0,0,307,305,1,0,0,0,307,308,1,0,0,0,308,311,1,0,0,0,309,307,1,0,0,0,310, + 312,3,40,20,0,311,310,1,0,0,0,311,312,1,0,0,0,312,33,1,0,0,0,313,314,3, + 36,18,0,314,315,5,104,0,0,315,316,3,38,19,0,316,319,1,0,0,0,317,319,3,38, + 19,0,318,313,1,0,0,0,318,317,1,0,0,0,319,35,1,0,0,0,320,321,5,76,0,0,321, + 37,1,0,0,0,322,323,7,2,0,0,323,39,1,0,0,0,324,327,3,42,21,0,325,327,3,44, + 22,0,326,324,1,0,0,0,326,325,1,0,0,0,327,41,1,0,0,0,328,329,5,75,0,0,329, + 334,5,76,0,0,330,331,5,34,0,0,331,333,5,76,0,0,332,330,1,0,0,0,333,336, + 1,0,0,0,334,332,1,0,0,0,334,335,1,0,0,0,335,43,1,0,0,0,336,334,1,0,0,0, + 337,338,5,65,0,0,338,339,3,42,21,0,339,340,5,66,0,0,340,45,1,0,0,0,341, + 342,5,20,0,0,342,347,3,34,17,0,343,344,5,34,0,0,344,346,3,34,17,0,345,343, + 1,0,0,0,346,349,1,0,0,0,347,345,1,0,0,0,347,348,1,0,0,0,348,351,1,0,0,0, + 349,347,1,0,0,0,350,352,3,28,14,0,351,350,1,0,0,0,351,352,1,0,0,0,352,355, + 1,0,0,0,353,354,5,29,0,0,354,356,3,28,14,0,355,353,1,0,0,0,355,356,1,0, + 0,0,356,47,1,0,0,0,357,358,5,4,0,0,358,359,3,28,14,0,359,49,1,0,0,0,360, + 362,5,15,0,0,361,363,3,28,14,0,362,361,1,0,0,0,362,363,1,0,0,0,363,366, + 1,0,0,0,364,365,5,29,0,0,365,367,3,28,14,0,366,364,1,0,0,0,366,367,1,0, + 0,0,367,51,1,0,0,0,368,373,3,66,33,0,369,370,5,36,0,0,370,372,3,66,33,0, + 371,369,1,0,0,0,372,375,1,0,0,0,373,371,1,0,0,0,373,374,1,0,0,0,374,53, + 1,0,0,0,375,373,1,0,0,0,376,381,3,60,30,0,377,378,5,36,0,0,378,380,3,60, + 30,0,379,377,1,0,0,0,380,383,1,0,0,0,381,379,1,0,0,0,381,382,1,0,0,0,382, + 55,1,0,0,0,383,381,1,0,0,0,384,389,3,54,27,0,385,386,5,34,0,0,386,388,3, + 54,27,0,387,385,1,0,0,0,388,391,1,0,0,0,389,387,1,0,0,0,389,390,1,0,0,0, + 390,57,1,0,0,0,391,389,1,0,0,0,392,393,7,3,0,0,393,59,1,0,0,0,394,397,5, + 80,0,0,395,397,3,64,32,0,396,394,1,0,0,0,396,395,1,0,0,0,397,61,1,0,0,0, + 398,441,5,45,0,0,399,400,3,98,49,0,400,401,5,67,0,0,401,441,1,0,0,0,402, + 441,3,96,48,0,403,441,3,98,49,0,404,441,3,92,46,0,405,441,3,64,32,0,406, + 441,3,100,50,0,407,408,5,65,0,0,408,413,3,94,47,0,409,410,5,34,0,0,410, + 412,3,94,47,0,411,409,1,0,0,0,412,415,1,0,0,0,413,411,1,0,0,0,413,414,1, + 0,0,0,414,416,1,0,0,0,415,413,1,0,0,0,416,417,5,66,0,0,417,441,1,0,0,0, + 418,419,5,65,0,0,419,424,3,92,46,0,420,421,5,34,0,0,421,423,3,92,46,0,422, + 420,1,0,0,0,423,426,1,0,0,0,424,422,1,0,0,0,424,425,1,0,0,0,425,427,1,0, + 0,0,426,424,1,0,0,0,427,428,5,66,0,0,428,441,1,0,0,0,429,430,5,65,0,0,430, + 435,3,100,50,0,431,432,5,34,0,0,432,434,3,100,50,0,433,431,1,0,0,0,434, + 437,1,0,0,0,435,433,1,0,0,0,435,436,1,0,0,0,436,438,1,0,0,0,437,435,1,0, + 0,0,438,439,5,66,0,0,439,441,1,0,0,0,440,398,1,0,0,0,440,399,1,0,0,0,440, + 402,1,0,0,0,440,403,1,0,0,0,440,404,1,0,0,0,440,405,1,0,0,0,440,406,1,0, + 0,0,440,407,1,0,0,0,440,418,1,0,0,0,440,429,1,0,0,0,441,63,1,0,0,0,442, + 445,5,48,0,0,443,445,5,64,0,0,444,442,1,0,0,0,444,443,1,0,0,0,445,65,1, + 0,0,0,446,449,3,58,29,0,447,449,3,64,32,0,448,446,1,0,0,0,448,447,1,0,0, + 0,449,67,1,0,0,0,450,451,5,9,0,0,451,452,5,27,0,0,452,69,1,0,0,0,453,454, + 5,14,0,0,454,459,3,72,36,0,455,456,5,34,0,0,456,458,3,72,36,0,457,455,1, + 0,0,0,458,461,1,0,0,0,459,457,1,0,0,0,459,460,1,0,0,0,460,71,1,0,0,0,461, + 459,1,0,0,0,462,464,3,10,5,0,463,465,7,4,0,0,464,463,1,0,0,0,464,465,1, + 0,0,0,465,468,1,0,0,0,466,467,5,46,0,0,467,469,7,5,0,0,468,466,1,0,0,0, + 468,469,1,0,0,0,469,73,1,0,0,0,470,471,5,8,0,0,471,472,3,56,28,0,472,75, + 1,0,0,0,473,474,5,2,0,0,474,475,3,56,28,0,475,77,1,0,0,0,476,477,5,11,0, + 0,477,482,3,80,40,0,478,479,5,34,0,0,479,481,3,80,40,0,480,478,1,0,0,0, + 481,484,1,0,0,0,482,480,1,0,0,0,482,483,1,0,0,0,483,79,1,0,0,0,484,482, + 1,0,0,0,485,486,3,54,27,0,486,487,5,84,0,0,487,488,3,54,27,0,488,81,1,0, + 0,0,489,490,5,1,0,0,490,491,3,20,10,0,491,493,3,100,50,0,492,494,3,88,44, + 0,493,492,1,0,0,0,493,494,1,0,0,0,494,83,1,0,0,0,495,496,5,7,0,0,496,497, + 3,20,10,0,497,498,3,100,50,0,498,85,1,0,0,0,499,500,5,10,0,0,500,501,3, + 52,26,0,501,87,1,0,0,0,502,507,3,90,45,0,503,504,5,34,0,0,504,506,3,90, + 45,0,505,503,1,0,0,0,506,509,1,0,0,0,507,505,1,0,0,0,507,508,1,0,0,0,508, + 89,1,0,0,0,509,507,1,0,0,0,510,511,3,58,29,0,511,512,5,32,0,0,512,513,3, + 62,31,0,513,91,1,0,0,0,514,515,7,6,0,0,515,93,1,0,0,0,516,519,3,96,48,0, + 517,519,3,98,49,0,518,516,1,0,0,0,518,517,1,0,0,0,519,95,1,0,0,0,520,522, + 7,0,0,0,521,520,1,0,0,0,521,522,1,0,0,0,522,523,1,0,0,0,523,524,5,28,0, + 0,524,97,1,0,0,0,525,527,7,0,0,0,526,525,1,0,0,0,526,527,1,0,0,0,527,528, + 1,0,0,0,528,529,5,27,0,0,529,99,1,0,0,0,530,531,5,26,0,0,531,101,1,0,0, + 0,532,533,7,7,0,0,533,103,1,0,0,0,534,535,5,5,0,0,535,536,3,106,53,0,536, + 105,1,0,0,0,537,538,5,65,0,0,538,539,3,2,1,0,539,540,5,66,0,0,540,107,1, + 0,0,0,541,542,5,13,0,0,542,543,5,100,0,0,543,109,1,0,0,0,544,545,5,3,0, + 0,545,548,5,90,0,0,546,547,5,88,0,0,547,549,3,54,27,0,548,546,1,0,0,0,548, + 549,1,0,0,0,549,559,1,0,0,0,550,551,5,89,0,0,551,556,3,112,56,0,552,553, + 5,34,0,0,553,555,3,112,56,0,554,552,1,0,0,0,555,558,1,0,0,0,556,554,1,0, + 0,0,556,557,1,0,0,0,557,560,1,0,0,0,558,556,1,0,0,0,559,550,1,0,0,0,559, + 560,1,0,0,0,560,111,1,0,0,0,561,562,3,54,27,0,562,563,5,32,0,0,563,565, + 1,0,0,0,564,561,1,0,0,0,564,565,1,0,0,0,565,566,1,0,0,0,566,567,3,54,27, + 0,567,113,1,0,0,0,568,569,5,18,0,0,569,570,3,34,17,0,570,571,5,88,0,0,571, + 572,3,56,28,0,572,115,1,0,0,0,573,574,5,17,0,0,574,577,3,28,14,0,575,576, + 5,29,0,0,576,578,3,28,14,0,577,575,1,0,0,0,577,578,1,0,0,0,578,117,1,0, + 0,0,56,129,138,156,168,177,185,191,199,201,206,213,218,229,235,243,245, + 256,263,274,277,291,299,307,311,318,326,334,347,351,355,362,366,373,381, + 389,396,413,424,435,440,444,448,459,464,468,482,493,507,518,521,526,548, + 556,559,564,577]; private static __ATN: ATN; public static get _ATN(): ATN { @@ -3626,9 +3640,6 @@ export class SourceCommandContext extends ParserRuleContext { public fromCommand(): FromCommandContext { return this.getTypedRuleContext(FromCommandContext, 0) as FromCommandContext; } - public metaCommand(): MetaCommandContext { - return this.getTypedRuleContext(MetaCommandContext, 0) as MetaCommandContext; - } public rowCommand(): RowCommandContext { return this.getTypedRuleContext(RowCommandContext, 0) as RowCommandContext; } @@ -4290,8 +4301,8 @@ export class FunctionExpressionContext extends ParserRuleContext { super(parent, invokingState); this.parser = parser; } - public identifier(): IdentifierContext { - return this.getTypedRuleContext(IdentifierContext, 0) as IdentifierContext; + public identifierOrParameter(): IdentifierOrParameterContext { + return this.getTypedRuleContext(IdentifierOrParameterContext, 0) as IdentifierOrParameterContext; } public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); @@ -4780,11 +4791,11 @@ export class QualifiedNameContext extends ParserRuleContext { super(parent, invokingState); this.parser = parser; } - public identifier_list(): IdentifierContext[] { - return this.getTypedRuleContexts(IdentifierContext) as IdentifierContext[]; + public identifierOrParameter_list(): IdentifierOrParameterContext[] { + return this.getTypedRuleContexts(IdentifierOrParameterContext) as IdentifierOrParameterContext[]; } - public identifier(i: number): IdentifierContext { - return this.getTypedRuleContext(IdentifierContext, i) as IdentifierContext; + public identifierOrParameter(i: number): IdentifierOrParameterContext { + return this.getTypedRuleContext(IdentifierOrParameterContext, i) as IdentifierOrParameterContext; } public DOT_list(): TerminalNode[] { return this.getTokens(esql_parser.DOT); @@ -4909,6 +4920,9 @@ export class IdentifierPatternContext extends ParserRuleContext { public ID_PATTERN(): TerminalNode { return this.getToken(esql_parser.ID_PATTERN, 0); } + public parameter(): ParameterContext { + return this.getTypedRuleContext(ParameterContext, 0) as ParameterContext; + } public get ruleIndex(): number { return esql_parser.RULE_identifierPattern; } @@ -5065,6 +5079,25 @@ export class StringArrayLiteralContext extends ConstantContext { } } } +export class InputParameterContext extends ConstantContext { + constructor(parser: esql_parser, ctx: ConstantContext) { + super(parser, ctx.parentCtx, ctx.invokingState); + super.copyFrom(ctx); + } + public parameter(): ParameterContext { + return this.getTypedRuleContext(ParameterContext, 0) as ParameterContext; + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterInputParameter) { + listener.enterInputParameter(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitInputParameter) { + listener.exitInputParameter(this); + } + } +} export class StringLiteralContext extends ConstantContext { constructor(parser: esql_parser, ctx: ConstantContext) { super(parser, ctx.parentCtx, ctx.invokingState); @@ -5118,25 +5151,6 @@ export class NumericArrayLiteralContext extends ConstantContext { } } } -export class InputParamsContext extends ConstantContext { - constructor(parser: esql_parser, ctx: ConstantContext) { - super(parser, ctx.parentCtx, ctx.invokingState); - super.copyFrom(ctx); - } - public params(): ParamsContext { - return this.getTypedRuleContext(ParamsContext, 0) as ParamsContext; - } - public enterRule(listener: esql_parserListener): void { - if(listener.enterInputParams) { - listener.enterInputParams(this); - } - } - public exitRule(listener: esql_parserListener): void { - if(listener.exitInputParams) { - listener.exitInputParams(this); - } - } -} export class IntegerLiteralContext extends ConstantContext { constructor(parser: esql_parser, ctx: ConstantContext) { super(parser, ctx.parentCtx, ctx.invokingState); @@ -5177,20 +5191,20 @@ export class BooleanLiteralContext extends ConstantContext { } -export class ParamsContext extends ParserRuleContext { +export class ParameterContext extends ParserRuleContext { constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { super(parent, invokingState); this.parser = parser; } public get ruleIndex(): number { - return esql_parser.RULE_params; + return esql_parser.RULE_parameter; } - public override copyFrom(ctx: ParamsContext): void { + public override copyFrom(ctx: ParameterContext): void { super.copyFrom(ctx); } } -export class InputNamedOrPositionalParamContext extends ParamsContext { - constructor(parser: esql_parser, ctx: ParamsContext) { +export class InputNamedOrPositionalParamContext extends ParameterContext { + constructor(parser: esql_parser, ctx: ParameterContext) { super(parser, ctx.parentCtx, ctx.invokingState); super.copyFrom(ctx); } @@ -5208,8 +5222,8 @@ export class InputNamedOrPositionalParamContext extends ParamsContext { } } } -export class InputParamContext extends ParamsContext { - constructor(parser: esql_parser, ctx: ParamsContext) { +export class InputParamContext extends ParameterContext { + constructor(parser: esql_parser, ctx: ParameterContext) { super(parser, ctx.parentCtx, ctx.invokingState); super.copyFrom(ctx); } @@ -5229,6 +5243,33 @@ export class InputParamContext extends ParamsContext { } +export class IdentifierOrParameterContext extends ParserRuleContext { + constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public identifier(): IdentifierContext { + return this.getTypedRuleContext(IdentifierContext, 0) as IdentifierContext; + } + public parameter(): ParameterContext { + return this.getTypedRuleContext(ParameterContext, 0) as ParameterContext; + } + public get ruleIndex(): number { + return esql_parser.RULE_identifierOrParameter; + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterIdentifierOrParameter) { + listener.enterIdentifierOrParameter(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitIdentifierOrParameter) { + listener.exitIdentifierOrParameter(this); + } + } +} + + export class LimitCommandContext extends ParserRuleContext { constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { super(parent, invokingState); @@ -5878,42 +5919,6 @@ export class ShowInfoContext extends ShowCommandContext { } -export class MetaCommandContext extends ParserRuleContext { - constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { - super(parent, invokingState); - this.parser = parser; - } - public get ruleIndex(): number { - return esql_parser.RULE_metaCommand; - } - public override copyFrom(ctx: MetaCommandContext): void { - super.copyFrom(ctx); - } -} -export class MetaFunctionsContext extends MetaCommandContext { - constructor(parser: esql_parser, ctx: MetaCommandContext) { - super(parser, ctx.parentCtx, ctx.invokingState); - super.copyFrom(ctx); - } - public META(): TerminalNode { - return this.getToken(esql_parser.META, 0); - } - public FUNCTIONS(): TerminalNode { - return this.getToken(esql_parser.FUNCTIONS, 0); - } - public enterRule(listener: esql_parserListener): void { - if(listener.enterMetaFunctions) { - listener.enterMetaFunctions(this); - } - } - public exitRule(listener: esql_parserListener): void { - if(listener.exitMetaFunctions) { - listener.exitMetaFunctions(this); - } - } -} - - export class EnrichCommandContext extends ParserRuleContext { public _policyName!: Token; public _matchField!: QualifiedNamePatternContext; diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts b/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts index 00a5596944960..f5c54adbe18d5 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts @@ -62,13 +62,14 @@ import { QualifiedIntegerLiteralContext } from "./esql_parser.js"; import { DecimalLiteralContext } from "./esql_parser.js"; import { IntegerLiteralContext } from "./esql_parser.js"; import { BooleanLiteralContext } from "./esql_parser.js"; -import { InputParamsContext } from "./esql_parser.js"; +import { InputParameterContext } from "./esql_parser.js"; import { StringLiteralContext } from "./esql_parser.js"; import { NumericArrayLiteralContext } from "./esql_parser.js"; import { BooleanArrayLiteralContext } from "./esql_parser.js"; import { StringArrayLiteralContext } from "./esql_parser.js"; import { InputParamContext } from "./esql_parser.js"; import { InputNamedOrPositionalParamContext } from "./esql_parser.js"; +import { IdentifierOrParameterContext } from "./esql_parser.js"; import { LimitCommandContext } from "./esql_parser.js"; import { SortCommandContext } from "./esql_parser.js"; import { OrderExpressionContext } from "./esql_parser.js"; @@ -90,7 +91,6 @@ import { ComparisonOperatorContext } from "./esql_parser.js"; import { ExplainCommandContext } from "./esql_parser.js"; import { SubqueryExpressionContext } from "./esql_parser.js"; import { ShowInfoContext } from "./esql_parser.js"; -import { MetaFunctionsContext } from "./esql_parser.js"; import { EnrichCommandContext } from "./esql_parser.js"; import { EnrichWithClauseContext } from "./esql_parser.js"; import { LookupCommandContext } from "./esql_parser.js"; @@ -653,17 +653,17 @@ export default class esql_parserListener extends ParseTreeListener { */ exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; /** - * Enter a parse tree produced by the `inputParams` + * Enter a parse tree produced by the `inputParameter` * labeled alternative in `esql_parser.constant`. * @param ctx the parse tree */ - enterInputParams?: (ctx: InputParamsContext) => void; + enterInputParameter?: (ctx: InputParameterContext) => void; /** - * Exit a parse tree produced by the `inputParams` + * Exit a parse tree produced by the `inputParameter` * labeled alternative in `esql_parser.constant`. * @param ctx the parse tree */ - exitInputParams?: (ctx: InputParamsContext) => void; + exitInputParameter?: (ctx: InputParameterContext) => void; /** * Enter a parse tree produced by the `stringLiteral` * labeled alternative in `esql_parser.constant`. @@ -714,28 +714,38 @@ export default class esql_parserListener extends ParseTreeListener { exitStringArrayLiteral?: (ctx: StringArrayLiteralContext) => void; /** * Enter a parse tree produced by the `inputParam` - * labeled alternative in `esql_parser.params`. + * labeled alternative in `esql_parser.parameter`. * @param ctx the parse tree */ enterInputParam?: (ctx: InputParamContext) => void; /** * Exit a parse tree produced by the `inputParam` - * labeled alternative in `esql_parser.params`. + * labeled alternative in `esql_parser.parameter`. * @param ctx the parse tree */ exitInputParam?: (ctx: InputParamContext) => void; /** * Enter a parse tree produced by the `inputNamedOrPositionalParam` - * labeled alternative in `esql_parser.params`. + * labeled alternative in `esql_parser.parameter`. * @param ctx the parse tree */ enterInputNamedOrPositionalParam?: (ctx: InputNamedOrPositionalParamContext) => void; /** * Exit a parse tree produced by the `inputNamedOrPositionalParam` - * labeled alternative in `esql_parser.params`. + * labeled alternative in `esql_parser.parameter`. * @param ctx the parse tree */ exitInputNamedOrPositionalParam?: (ctx: InputNamedOrPositionalParamContext) => void; + /** + * Enter a parse tree produced by `esql_parser.identifierOrParameter`. + * @param ctx the parse tree + */ + enterIdentifierOrParameter?: (ctx: IdentifierOrParameterContext) => void; + /** + * Exit a parse tree produced by `esql_parser.identifierOrParameter`. + * @param ctx the parse tree + */ + exitIdentifierOrParameter?: (ctx: IdentifierOrParameterContext) => void; /** * Enter a parse tree produced by `esql_parser.limitCommand`. * @param ctx the parse tree @@ -948,18 +958,6 @@ export default class esql_parserListener extends ParseTreeListener { * @param ctx the parse tree */ exitShowInfo?: (ctx: ShowInfoContext) => void; - /** - * Enter a parse tree produced by the `metaFunctions` - * labeled alternative in `esql_parser.metaCommand`. - * @param ctx the parse tree - */ - enterMetaFunctions?: (ctx: MetaFunctionsContext) => void; - /** - * Exit a parse tree produced by the `metaFunctions` - * labeled alternative in `esql_parser.metaCommand`. - * @param ctx the parse tree - */ - exitMetaFunctions?: (ctx: MetaFunctionsContext) => void; /** * Enter a parse tree produced by `esql_parser.enrichCommand`. * @param ctx the parse tree diff --git a/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts index b140a4dc83ed1..30d44d447387e 100644 --- a/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts +++ b/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts @@ -29,24 +29,6 @@ describe('commands', () => { ]); }); - it('META', () => { - const query = 'META functions'; - const { ast } = parse(query); - - expect(ast).toMatchObject([ - { - type: 'command', - name: 'meta', - args: [ - { - type: 'function', - name: 'functions', - }, - ], - }, - ]); - }); - it('FROM', () => { const query = 'FROM index'; const { ast } = parse(query); diff --git a/packages/kbn-esql-ast/src/parser/esql_ast_builder_listener.ts b/packages/kbn-esql-ast/src/parser/esql_ast_builder_listener.ts index 88248a0e0bf20..de406e33aa7a5 100644 --- a/packages/kbn-esql-ast/src/parser/esql_ast_builder_listener.ts +++ b/packages/kbn-esql-ast/src/parser/esql_ast_builder_listener.ts @@ -10,7 +10,6 @@ import type { ErrorNode, ParserRuleContext, TerminalNode } from 'antlr4'; import { type ShowInfoContext, - type MetaFunctionsContext, type SingleStatementContext, type RowCommandContext, type FromCommandContext, @@ -28,7 +27,6 @@ import { type EnrichCommandContext, type WhereCommandContext, default as esql_parser, - type MetaCommandContext, type MetricsCommandContext, IndexPatternContext, InlinestatsCommandContext, @@ -83,21 +81,6 @@ export class ESQLAstBuilderListener implements ESQLParserListener { } } - /** - * Exit a parse tree produced by the `showFunctions` - * labeled alternative in `esql_parser.showCommand`. - * @param ctx the parse tree - */ - exitMetaFunctions(ctx: MetaFunctionsContext) { - const commandAst = createCommand('meta', ctx); - this.ast.push(commandAst); - // update the text - commandAst.text = ctx.getText(); - if (textExistsAndIsValid(ctx.FUNCTIONS().getText())) { - commandAst?.args.push(createFunction('functions', ctx, getPosition(ctx.FUNCTIONS().symbol))); - } - } - /** * Enter a parse tree produced by `esql_parser.singleStatement`. * @param ctx the parse tree @@ -310,14 +293,6 @@ export class ESQLAstBuilderListener implements ESQLParserListener { this.ast.push(command); } - /** - * Enter a parse tree produced by `esql_parser.metaCommand`. - * @param ctx the parse tree - */ - enterMetaCommand(ctx: MetaCommandContext) { - const command = createCommand('meta', ctx); - this.ast.push(command); - } /** * Exit a parse tree produced by `esql_parser.enrichCommand`. * @param ctx the parse tree diff --git a/packages/kbn-esql-ast/src/parser/factories.ts b/packages/kbn-esql-ast/src/parser/factories.ts index 5afc23a1bd5d6..321ca6a40dcd0 100644 --- a/packages/kbn-esql-ast/src/parser/factories.ts +++ b/packages/kbn-esql-ast/src/parser/factories.ts @@ -430,7 +430,9 @@ export function createColumn(ctx: ParserRuleContext): ESQLColumn { ...ctx.identifierPattern_list().map((identifier) => parseIdentifier(identifier.getText())) ); } else if (ctx instanceof QualifiedNameContext) { - parts.push(...ctx.identifier_list().map((identifier) => parseIdentifier(identifier.getText()))); + parts.push( + ...ctx.identifierOrParameter_list().map((identifier) => parseIdentifier(identifier.getText())) + ); } else { parts.push(sanitizeIdentifierString(ctx)); } diff --git a/packages/kbn-esql-ast/src/parser/parser.ts b/packages/kbn-esql-ast/src/parser/parser.ts index 612239f97215e..ad263a49ebd00 100644 --- a/packages/kbn-esql-ast/src/parser/parser.ts +++ b/packages/kbn-esql-ast/src/parser/parser.ts @@ -64,7 +64,7 @@ export const createParser = (text: string) => { // These will need to be manually updated whenever the relevant grammar changes. const SYNTAX_ERRORS_TO_IGNORE = [ - `SyntaxError: mismatched input '' expecting {'explain', 'from', 'meta', 'row', 'show'}`, + `SyntaxError: mismatched input '' expecting {'explain', 'from', 'row', 'show'}`, ]; export interface ParseOptions { diff --git a/packages/kbn-esql-ast/src/parser/walkers.ts b/packages/kbn-esql-ast/src/parser/walkers.ts index ce9490ccf545c..cccc215ec365e 100644 --- a/packages/kbn-esql-ast/src/parser/walkers.ts +++ b/packages/kbn-esql-ast/src/parser/walkers.ts @@ -16,7 +16,7 @@ import { BooleanDefaultContext, type BooleanExpressionContext, BooleanLiteralContext, - InputParamsContext, + InputParameterContext, BooleanValueContext, type CommandOptionsContext, ComparisonContext, @@ -385,7 +385,7 @@ function getConstant(ctx: ConstantContext): ESQLAstItem { } return createList(ctx, values); } - if (ctx instanceof InputParamsContext && ctx.children) { + if (ctx instanceof InputParameterContext && ctx.children) { const values: ESQLLiteral[] = []; for (const child of ctx.children) { @@ -478,7 +478,7 @@ export function visitPrimaryExpression(ctx: PrimaryExpressionContext): ESQLAstIt if (ctx instanceof FunctionContext) { const functionExpressionCtx = ctx.functionExpression(); const fn = createFunction( - functionExpressionCtx.identifier().getText().toLowerCase(), + functionExpressionCtx.identifierOrParameter().getText().toLowerCase(), ctx, undefined, 'variadic-call' diff --git a/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts b/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts index af54b8ccf36fb..20db9e729f094 100644 --- a/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts +++ b/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts @@ -87,15 +87,6 @@ describe('single line query', () => { }); }); - describe('META', () => { - /** @todo Enable once show command args are parsed as columns. */ - test.skip('functions page', () => { - const { text } = reprint('META functions'); - - expect(text).toBe('META functions'); - }); - }); - describe('STATS', () => { test('with aggregates assignment', () => { const { text } = reprint('FROM a | STATS var = agg(123, fn(true))'); diff --git a/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts b/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts index eb116bb77d904..29d63387a40e2 100644 --- a/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts +++ b/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts @@ -44,7 +44,7 @@ export async function getESQLAdHocDataview( dataView.timeFieldName = timeField; - // If the indexPattern is empty string means that the user used either the ROW or META FUNCTIONS / SHOW INFO commands + // If the indexPattern is empty string means that the user used either the ROW, SHOW INFO commands // we don't want to add the @timestamp field in this case https://github.com/elastic/kibana/issues/163417 if (!timeField && indexPattern && dataView?.fields?.getByName?.('@timestamp')?.type === 'date') { dataView.timeFieldName = '@timestamp'; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts index 3da63848168a3..491c44fe699df 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts @@ -18,7 +18,7 @@ export const validationFromCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('f', [ - "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'meta', 'row', 'show'}", + "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'row', 'show'}", ]); await expectErrors('from ', [ "SyntaxError: mismatched input '' expecting {QUOTED_STRING, UNQUOTED_SOURCE}", diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts index 8dd1634f63279..5384fdc136b4e 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts @@ -17,7 +17,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('m', [ - "SyntaxError: mismatched input 'm' expecting {'explain', 'from', 'meta', 'row', 'show'}", + "SyntaxError: mismatched input 'm' expecting {'explain', 'from', 'row', 'show'}", ]); await expectErrors('metrics ', [ "SyntaxError: mismatched input '' expecting {QUOTED_STRING, UNQUOTED_SOURCE}", diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index 43a42f0270b74..736159b36384d 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -160,63 +160,63 @@ { "query": "eval", "error": [ - "SyntaxError: mismatched input 'eval' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'eval' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "stats", "error": [ - "SyntaxError: mismatched input 'stats' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'stats' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "rename", "error": [ - "SyntaxError: mismatched input 'rename' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'rename' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "limit", "error": [ - "SyntaxError: mismatched input 'limit' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'limit' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "keep", "error": [ - "SyntaxError: mismatched input 'keep' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'keep' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "drop", "error": [ - "SyntaxError: mismatched input 'drop' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'drop' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "mv_expand", "error": [ - "SyntaxError: mismatched input 'mv_expand' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'mv_expand' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "dissect", "error": [ - "SyntaxError: mismatched input 'dissect' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'dissect' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, { "query": "grok", "error": [ - "SyntaxError: mismatched input 'grok' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'grok' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, @@ -2206,7 +2206,7 @@ { "query": "from index | keep ", "error": [ - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -2225,8 +2225,9 @@ "error": [ "SyntaxError: token recognition error at: '4'", "SyntaxError: token recognition error at: '5'", - "SyntaxError: missing ID_PATTERN at '.'", - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '.' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "Unknown column [.]" ], "warning": [] }, @@ -2336,7 +2337,7 @@ { "query": "from index | drop ", "error": [ - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -2350,8 +2351,9 @@ "error": [ "SyntaxError: token recognition error at: '4'", "SyntaxError: token recognition error at: '5'", - "SyntaxError: missing ID_PATTERN at '.'", - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '.' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "Unknown column [.]" ], "warning": [] }, @@ -2471,7 +2473,7 @@ { "query": "from a_index | mv_expand ", "error": [ - "SyntaxError: missing {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER} at ''" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -2531,7 +2533,7 @@ { "query": "from a_index | rename", "error": [ - "SyntaxError: mismatched input '' expecting ID_PATTERN" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -2553,14 +2555,14 @@ { "query": "from a_index | rename textField as", "error": [ - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, { "query": "from a_index | rename missingField as", "error": [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", "Unknown column [missingField]" ], "warning": [] @@ -2608,7 +2610,7 @@ { "query": "from a_index |eval doubleField + 1 | rename `doubleField + 1` as ", "error": [ - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -2664,7 +2666,7 @@ { "query": "from a_index | dissect textField .", "error": [ - "SyntaxError: mismatched input '' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Unknown column [textField.]" ], "warning": [] @@ -2759,7 +2761,7 @@ { "query": "from a_index | grok textField .", "error": [ - "SyntaxError: mismatched input '' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Unknown column [textField.]" ], "warning": [] @@ -9371,7 +9373,7 @@ { "query": "from a_index |enrich policy on ", "error": [ - "SyntaxError: missing ID_PATTERN at ''" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -9400,7 +9402,7 @@ { "query": "from a_index | enrich policy on textField with ", "error": [ - "SyntaxError: mismatched input '' expecting ID_PATTERN" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -9414,7 +9416,7 @@ { "query": "from a_index |enrich policy on doubleField with var0 = ", "error": [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", "Unknown column [var0]" ], "warning": [] @@ -9430,8 +9432,8 @@ { "query": "from a_index |enrich policy on doubleField with var0 = , ", "error": [ - "SyntaxError: missing ID_PATTERN at ','", - "SyntaxError: mismatched input '' expecting ID_PATTERN", + "SyntaxError: mismatched input ',' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", "Unknown column [var0]" ], "warning": [] @@ -9456,7 +9458,7 @@ { "query": "from a_index |enrich policy on doubleField with var0 = otherField, var1 = ", "error": [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", "Unknown column [var1]" ], "warning": [] @@ -9474,7 +9476,7 @@ { "query": "from a_index | enrich policy with ", "error": [ - "SyntaxError: mismatched input '' expecting ID_PATTERN" + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}" ], "warning": [] }, @@ -9688,7 +9690,7 @@ { "query": "f", "error": [ - "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'meta', 'row', 'show'}" + "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'row', 'show'}" ], "warning": [] }, diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 6e009d081c33a..66de6c7fc70ad 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -276,7 +276,7 @@ describe('validation logic', () => { ['eval', 'stats', 'rename', 'limit', 'keep', 'drop', 'mv_expand', 'dissect', 'grok'].map( (command) => testErrorsAndWarnings(command, [ - `SyntaxError: mismatched input '${command}' expecting {'explain', 'from', 'meta', 'row', 'show'}`, + `SyntaxError: mismatched input '${command}' expecting {'explain', 'from', 'row', 'show'}`, ]) ); }); @@ -511,7 +511,9 @@ describe('validation logic', () => { }); describe('keep', () => { - testErrorsAndWarnings('from index | keep ', ["SyntaxError: missing ID_PATTERN at ''"]); + testErrorsAndWarnings('from index | keep ', [ + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + ]); testErrorsAndWarnings( 'from index | keep keywordField, doubleField, integerField, dateField', [] @@ -523,8 +525,9 @@ describe('validation logic', () => { testErrorsAndWarnings('from index | keep 4.5', [ "SyntaxError: token recognition error at: '4'", "SyntaxError: token recognition error at: '5'", - "SyntaxError: missing ID_PATTERN at '.'", - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '.' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + 'Unknown column [.]', ]); testErrorsAndWarnings('from index | keep `4.5`', ['Unknown column [4.5]']); testErrorsAndWarnings('from index | keep missingField, doubleField, dateField', [ @@ -563,13 +566,16 @@ describe('validation logic', () => { }); describe('drop', () => { - testErrorsAndWarnings('from index | drop ', ["SyntaxError: missing ID_PATTERN at ''"]); + testErrorsAndWarnings('from index | drop ', [ + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + ]); testErrorsAndWarnings('from index | drop textField, doubleField, dateField', []); testErrorsAndWarnings('from index | drop 4.5', [ "SyntaxError: token recognition error at: '4'", "SyntaxError: token recognition error at: '5'", - "SyntaxError: missing ID_PATTERN at '.'", - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '.' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + 'Unknown column [.]', ]); testErrorsAndWarnings('from index | drop missingField, doubleField, dateField', [ 'Unknown column [missingField]', @@ -612,7 +618,7 @@ describe('validation logic', () => { describe('mv_expand', () => { testErrorsAndWarnings('from a_index | mv_expand ', [ - "SyntaxError: missing {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER} at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); for (const type of ['text', 'integer', 'date', 'boolean', 'ip']) { testErrorsAndWarnings(`from a_index | mv_expand ${type}Field`, []); @@ -631,7 +637,7 @@ describe('validation logic', () => { describe('rename', () => { testErrorsAndWarnings('from a_index | rename', [ - "SyntaxError: mismatched input '' expecting ID_PATTERN", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", ]); testErrorsAndWarnings('from a_index | rename textField', [ "SyntaxError: mismatched input '' expecting 'as'", @@ -641,10 +647,10 @@ describe('validation logic', () => { 'Unknown column [a]', ]); testErrorsAndWarnings('from a_index | rename textField as', [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", ]); testErrorsAndWarnings('from a_index | rename missingField as', [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", 'Unknown column [missingField]', ]); testErrorsAndWarnings('from a_index | rename textField as b', []); @@ -666,7 +672,7 @@ describe('validation logic', () => { [] ); testErrorsAndWarnings('from a_index |eval doubleField + 1 | rename `doubleField + 1` as ', [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", ]); testErrorsAndWarnings('from a_index | rename key* as keywords', [ 'Using wildcards (*) in RENAME is not allowed [key*]', @@ -693,7 +699,7 @@ describe('validation logic', () => { "SyntaxError: mismatched input '2' expecting QUOTED_STRING", ]); testErrorsAndWarnings('from a_index | dissect textField .', [ - "SyntaxError: mismatched input '' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Unknown column [textField.]', ]); testErrorsAndWarnings('from a_index | dissect textField %a', [ @@ -744,7 +750,7 @@ describe('validation logic', () => { "SyntaxError: mismatched input '2' expecting QUOTED_STRING", ]); testErrorsAndWarnings('from a_index | grok textField .', [ - "SyntaxError: mismatched input '' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Unknown column [textField.]', ]); testErrorsAndWarnings('from a_index | grok textField %a', [ @@ -1390,7 +1396,7 @@ describe('validation logic', () => { 'Unknown policy [missing-policy]', ]); testErrorsAndWarnings(`from a_index |enrich policy on `, [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", ]); testErrorsAndWarnings(`from a_index | enrich policy on b `, ['Unknown column [b]']); @@ -1402,13 +1408,13 @@ describe('validation logic', () => { 'Unknown column [this]', ]); testErrorsAndWarnings(`from a_index | enrich policy on textField with `, [ - "SyntaxError: mismatched input '' expecting ID_PATTERN", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", ]); testErrorsAndWarnings(`from a_index | enrich policy on textField with var0 `, [ 'Unknown column [var0]', ]); testErrorsAndWarnings(`from a_index |enrich policy on doubleField with var0 = `, [ - "SyntaxError: missing ID_PATTERN at ''", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", 'Unknown column [var0]', ]); testErrorsAndWarnings(`from a_index | enrich policy on textField with var0 = c `, [ @@ -1420,8 +1426,8 @@ describe('validation logic', () => { // `Unknown column [textField]`, // ]); testErrorsAndWarnings(`from a_index |enrich policy on doubleField with var0 = , `, [ - "SyntaxError: missing ID_PATTERN at ','", - "SyntaxError: mismatched input '' expecting ID_PATTERN", + "SyntaxError: mismatched input ',' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", 'Unknown column [var0]', ]); testErrorsAndWarnings( @@ -1438,7 +1444,10 @@ describe('validation logic', () => { ); testErrorsAndWarnings( `from a_index |enrich policy on doubleField with var0 = otherField, var1 = `, - ["SyntaxError: missing ID_PATTERN at ''", 'Unknown column [var1]'] + [ + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", + 'Unknown column [var1]', + ] ); testErrorsAndWarnings( @@ -1450,7 +1459,7 @@ describe('validation logic', () => { [] ); testErrorsAndWarnings(`from a_index | enrich policy with `, [ - "SyntaxError: mismatched input '' expecting ID_PATTERN", + "SyntaxError: mismatched input '' expecting {'?', NAMED_OR_POSITIONAL_PARAM, ID_PATTERN}", ]); testErrorsAndWarnings(`from a_index | enrich policy with otherField`, []); testErrorsAndWarnings(`from a_index | enrich policy | eval otherField`, []); diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts index 46f4162b29dbc..237996a7fbcaa 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts @@ -91,7 +91,6 @@ describe('ESQL Theme', () => { 'lookup_ws', 'lookup_field_ws', 'show_ws', - 'meta_ws', 'setting', 'setting_ws', 'metrics_ws', diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.ts index f2537474a1b25..f98eddefd8eab 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.ts @@ -46,7 +46,6 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ ...buildRuleGroup( [ 'dev_metrics', - 'meta', 'metadata', 'dev_match', 'mv_expand', @@ -135,8 +134,6 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'lookup_field_multiline_comment', 'show_line_comment', 'show_multiline_comment', - 'meta_line_comment', - 'meta_multiline_comment', 'setting', 'setting_line_comment', 'settting_multiline_comment', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts index b4a7e5b9997e1..9dcca93d8fdd2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts @@ -111,7 +111,7 @@ describe('parseEsqlQuery', () => { errors: expect.arrayContaining([ expect.objectContaining({ message: - "SyntaxError: mismatched input 'aaa' expecting {'explain', 'from', 'meta', 'row', 'show'}", + "SyntaxError: mismatched input 'aaa' expecting {'explain', 'from', 'row', 'show'}", }), ]), isEsqlQueryAggregating: false, From 343a33a637dfc2b2f68a3e35cc69bcc4f0566ced Mon Sep 17 00:00:00 2001 From: Sid Date: Tue, 15 Oct 2024 16:03:07 +0200 Subject: [PATCH 29/84] Harden API Actions Definition standards (#193140) Closes https://github.com/elastic/kibana/issues/191716 ## Summary This PR introduces a new signature for the API Actions `get` function that validates standard API operations as part of the name of the API action. ### Changes - Added a new Enum for a standard set of operations we expect all API actions to move to - Old function signature based on a single subject marked as deprecated. ### Release Notes Enforce standard on API Actions definitions by separating operations and subjects. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine Co-authored-by: Elena Shostak --- .../server/routes/telemetry_usage_stats.ts | 3 +- src/plugins/telemetry/tsconfig.json | 1 + .../authorization_core/src/actions/api.ts | 31 ++- .../src/privileges/privileges.test.ts | 189 ++++++++++-------- .../src/privileges/privileges.ts | 11 +- .../security/plugin_types_server/index.ts | 1 + .../src/authorization/actions/api.ts | 16 +- .../src/authorization/actions/index.ts | 1 + .../src/authorization/index.ts | 1 + .../plugins/features/server/routes/index.ts | 2 +- .../roles/get_all_by_space.test.ts | 2 +- .../authorization/roles/get_all_by_space.ts | 2 +- .../api/internal/get_content_summary.test.ts | 2 +- .../api/internal/get_content_summary.ts | 2 +- 14 files changed, 168 insertions(+), 96 deletions(-) diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index 843bf67e7863c..f19ec804ac6e9 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -14,6 +14,7 @@ import type { StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { RequestHandler } from '@kbn/core-http-server'; import { FetchSnapshotTelemetry } from '../../common/routes'; import { UsageStatsBody, v2 } from '../../common/types'; @@ -50,7 +51,7 @@ export function registerTelemetryUsageStatsRoutes( // security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only // granted to users that have "Global All" or "Global Read" privileges in Kibana. const { checkPrivilegesWithRequest, actions } = security.authz; - const privileges = { kibana: actions.api.get('decryptedTelemetry') }; + const privileges = { kibana: actions.api.get(ApiOperation.Read, 'decryptedTelemetry') }; const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges); if (!hasAllRequested) { return res.forbidden(); diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index 09d5aa25c914b..a8538b4a0b18a 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -36,6 +36,7 @@ "@kbn/analytics-collection-utils", "@kbn/react-kibana-mount", "@kbn/core-node-server", + "@kbn/security-plugin-types-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/packages/security/authorization_core/src/actions/api.ts b/x-pack/packages/security/authorization_core/src/actions/api.ts index fec6296d8f63f..d91bc1bd89669 100644 --- a/x-pack/packages/security/authorization_core/src/actions/api.ts +++ b/x-pack/packages/security/authorization_core/src/actions/api.ts @@ -8,6 +8,7 @@ import { isString } from 'lodash'; import type { ApiActions as ApiActionsType } from '@kbn/security-plugin-types-server'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; export class ApiActions implements ApiActionsType { private readonly prefix: string; @@ -16,11 +17,33 @@ export class ApiActions implements ApiActionsType { this.prefix = `api:`; } - public get(operation: string) { - if (!operation || !isString(operation)) { - throw new Error('operation is required and must be a string'); + private isValidOperation(operation: string): operation is ApiOperation { + return Object.values(ApiOperation).includes(operation as ApiOperation); + } + public actionFromRouteTag(routeTag: string) { + const [operation, subject] = routeTag.split('_'); + if (!this.isValidOperation(operation)) { + throw new Error('operation is required and must be a valid ApiOperation'); + } + return this.get(operation, subject); + } + + public get(operation: string | ApiOperation, subject?: string) { + if (arguments.length === 1) { + if (!isString(operation) || !operation) { + throw new Error('operation is required and must be a string'); + } + return `${this.prefix}${operation}`; + } + + if (!isString(subject) || !subject) { + throw new Error('subject is required and must be a string'); + } + + if (!this.isValidOperation(operation)) { + throw new Error('operation is required and must be a valid ApiOperation'); } - return `${this.prefix}${operation}`; + return `${this.prefix}${operation}_${subject}`; } } diff --git a/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts b/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts index f9d490bfcb09b..6af21d5357a72 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts @@ -7,6 +7,7 @@ import { KibanaFeature } from '@kbn/features-plugin/server'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { getReplacedByForPrivilege, privilegesFactory } from './privileges'; import { licenseMock } from '../__fixtures__/licensing.mock'; @@ -793,10 +794,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -965,10 +968,12 @@ describe('features', () => { const expectedActions = [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1124,7 +1129,9 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), actions.ui.get('catalogue', 'read-catalogue-1'), actions.ui.get('catalogue', 'read-catalogue-2'), @@ -1243,7 +1250,9 @@ describe('features', () => { const expectedActions = [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), actions.ui.get('catalogue', 'read-catalogue-2'), actions.ui.get('management', 'read-management', 'read-management-2'), @@ -1341,10 +1350,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1359,7 +1370,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1410,10 +1423,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1428,7 +1443,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1508,10 +1525,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1526,7 +1545,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1578,10 +1599,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1596,7 +1619,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1677,10 +1702,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1695,7 +1722,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1945,10 +1974,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -1960,7 +1989,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.ui.get('foo', 'foo'), ]); @@ -2104,10 +2133,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2137,7 +2166,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -2340,10 +2369,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2354,7 +2383,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), ]); @@ -2479,10 +2508,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2512,7 +2541,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.ui.get('foo', 'foo'), ]); @@ -2658,10 +2687,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2672,7 +2701,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), ]); @@ -2795,10 +2824,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2828,7 +2857,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -3010,10 +3039,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -3043,7 +3072,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -3244,10 +3273,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -3277,7 +3306,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -3514,10 +3543,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -3565,7 +3594,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), diff --git a/x-pack/packages/security/authorization_core/src/privileges/privileges.ts b/x-pack/packages/security/authorization_core/src/privileges/privileges.ts index 7f388e80defd2..b81eaba5fa54d 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/privileges.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/privileges.ts @@ -17,6 +17,7 @@ import { isMinimalPrivilegeId, } from '@kbn/security-authorization-core-common'; import type { RawKibanaPrivileges, SecurityLicense } from '@kbn/security-plugin-types-common'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { featurePrivilegeBuilderFactory } from './feature_privilege_builder'; import type { Actions } from '../actions'; @@ -210,10 +211,10 @@ export function privilegesFactory( global: { all: [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -225,7 +226,7 @@ export function privilegesFactory( ], read: [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), ...readActions, ], diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index 21ab0eb2b39af..2b46fa0146a2a 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -88,3 +88,4 @@ export { getRestApiKeyWithKibanaPrivilegesSchema, } from './src/authentication'; export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization'; +export { ApiOperation } from './src/authorization'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts b/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts index 30a1328ce5639..01fa535a1a0d5 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts @@ -6,5 +6,19 @@ */ export interface ApiActions { - get(operation: string): string; + get(operation: ApiOperation, subject: string): string; + + /** + * @deprecated use `get(operation: ApiOperation, subject: string)` instead + */ + get(subject: string): string; + actionFromRouteTag(routeTag: string): string; +} + +export enum ApiOperation { + Read = 'read', + Create = 'create', + Update = 'update', + Delete = 'delete', + Manage = 'manage', } diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts b/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts index 6b3993423015f..baed1cde4457e 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts @@ -8,6 +8,7 @@ export type { Actions } from './actions'; export type { AlertingActions } from './alerting'; export type { ApiActions } from './api'; +export { ApiOperation } from './api'; export type { AppActions } from './app'; export type { CasesActions } from './cases'; export type { SavedObjectActions } from './saved_object'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/index.ts b/x-pack/packages/security/plugin_types_server/src/authorization/index.ts index baeeeddc1fa74..c48e797dc1d1b 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/index.ts @@ -15,6 +15,7 @@ export type { SpaceActions, UIActions, } from './actions'; +export { ApiOperation } from './actions'; export type { AuthorizationServiceSetup } from './authorization_service'; export type { CheckPrivilegesOptions, diff --git a/x-pack/plugins/features/server/routes/index.ts b/x-pack/plugins/features/server/routes/index.ts index b0da6cf4a0659..281010613f693 100644 --- a/x-pack/plugins/features/server/routes/index.ts +++ b/x-pack/plugins/features/server/routes/index.ts @@ -22,7 +22,7 @@ export function defineRoutes({ router, featureRegistry }: RouteDefinitionParams) { path: '/api/features', options: { - tags: ['access:features'], + tags: ['access:read_features'], access: 'public', summary: `Get features`, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts index 5797948244dad..06d6d396ce022 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts @@ -149,7 +149,7 @@ describe('GET all roles by space id', () => { const paramsSchema = (config.validate as any).params; - expect(config.options).toEqual({ tags: ['access:manageSpaces'] }); + expect(config.options).toEqual({ tags: ['access:manage_spaces'] }); expect(() => paramsSchema.validate({})).toThrowErrorMatchingInlineSnapshot( `"[spaceId]: expected value of type [string] but got [undefined]"` ); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts index 48ec8e8f72461..734f0292db116 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts @@ -25,7 +25,7 @@ export function defineGetAllRolesBySpaceRoutes({ { path: '/internal/security/roles/{spaceId}', options: { - tags: ['access:manageSpaces'], + tags: ['access:manage_spaces'], }, validate: { params: schema.object({ spaceId: schema.string({ minLength: 1 }) }), diff --git a/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts b/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts index 3de451ddfa730..d6bc68244f750 100644 --- a/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts @@ -120,7 +120,7 @@ describe('GET /internal/spaces/{spaceId}/content_summary', () => { const paramsSchema = (config.validate as any).params; - expect(config.options).toEqual({ tags: ['access:manageSpaces'] }); + expect(config.options).toEqual({ tags: ['access:manage_spaces'] }); expect(() => paramsSchema.validate({})).toThrowErrorMatchingInlineSnapshot( `"[spaceId]: expected value of type [string] but got [undefined]"` ); diff --git a/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.ts b/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.ts index b582c304fd13b..848449bfd47b3 100644 --- a/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.ts +++ b/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.ts @@ -39,7 +39,7 @@ export function initGetSpaceContentSummaryApi(deps: InternalRouteDeps) { { path: '/internal/spaces/{spaceId}/content_summary', options: { - tags: ['access:manageSpaces'], + tags: ['access:manage_spaces'], }, validate: { params: schema.object({ From fc4e95730597bf7d1f3f5dcc7f3e26caa9ed85a1 Mon Sep 17 00:00:00 2001 From: Sergi Romeu Date: Tue, 15 Oct 2024 16:06:10 +0200 Subject: [PATCH 30/84] [APM] Create sub-feature role to manage APM settings write permissions (#194419) ## Summary Closes https://github.com/elastic/kibana/issues/156898 This PR adds a new sub-feature role to APM, which is the ability to write into the settings page: for UI, `settings:save`, for API, `apm_settings_write`. The other changes are adapting to use this new permission. ## How to test 1. Go under `Stack Management` -> `Roles` and create a new custom role. 3. For Kibana, select `All spaces` for the space selector, and `Customize`, you can get all the permissions you need. 4. Go into `Observability` and `APM and User Experience`. 5. Select `Read` and save the role. 6. Create a new user and assign that role and also the `viewer` role. 7. Login with an incognito / different browser into the new user. 8. Go into `APM` -> `Settings`, WARNING: if you are not able to see settings is because you don't have data, run `node scripts/synthtrace many_services.ts --live --clean`. 9. You should not be able to change the configuration on each tab. 10. Change the role privileges to have `Read` but with write access. 11. Test it, you should be able to modify the settings. 12. Do the same with `All` with and without the write permissions. ## Demo https://github.com/user-attachments/assets/5c1b6c33-4c2c-4616-bfe2-5b4bdc414e6a --- .../e2e/settings/agent_configurations.cy.ts | 134 +++-- .../cypress/e2e/settings/agent_keys.cy.ts | 102 ++++ .../e2e/settings/anomaly_detection.cy.ts | 122 +++++ .../cypress/e2e/settings/custom_links.cy.ts | 117 +++-- .../e2e/settings/general_settings.cy.ts | 63 +++ .../cypress/e2e/settings/indices.cy.ts | 64 +++ .../transaction_details.cy.ts | 13 +- .../apm/ftr_e2e/cypress/support/commands.ts | 14 + .../apm/ftr_e2e/cypress/support/types.d.ts | 2 + .../settings/agent_configurations/index.tsx | 2 +- .../agent_configurations/list/index.tsx | 2 +- .../settings/agent_keys/agent_keys_table.tsx | 12 +- .../app/settings/agent_keys/index.tsx | 64 ++- .../settings/anomaly_detection/jobs_list.tsx | 35 +- .../app/settings/apm_indices/index.tsx | 5 +- .../custom_link/create_custom_link_button.tsx | 2 +- .../custom_link/custom_link_table.tsx | 2 +- .../app/settings/custom_link/index.test.tsx | 2 +- .../app/settings/general_settings/index.tsx | 8 +- .../apm/server/feature.ts | 31 +- .../apm/server/routes/agent_keys/route.ts | 4 +- .../register_apm_server_routes.test.ts | 17 +- .../settings/agent_configuration/route.ts | 4 +- .../settings/anomaly_detection/route.ts | 4 +- .../routes/settings/apm_indices/route.ts | 2 +- .../routes/settings/custom_link/route.ts | 6 +- .../apm/server/routes/typings.ts | 1 + .../create_apm_users/authentication.ts | 41 ++ .../apis/security/privileges.ts | 2 +- .../apis/security/privileges_basic.ts | 2 +- .../test/apm_api_integration/common/config.ts | 18 +- .../agent_configuration.spec.ts | 464 ++++++++++++++++-- .../anomaly_detection/read_user.spec.ts | 65 ++- .../anomaly_detection/update_to_v3.spec.ts | 10 + .../anomaly_detection/write_user.spec.ts | 91 ++-- .../settings/apm_indices/apm_indices.spec.ts | 112 ++++- .../{ => custom_link}/custom_link.spec.ts | 165 ++++--- .../platform_security/authorization.ts | 7 + 38 files changed, 1499 insertions(+), 312 deletions(-) create mode 100644 x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_keys.cy.ts create mode 100644 x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/anomaly_detection.cy.ts create mode 100644 x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/general_settings.cy.ts create mode 100644 x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/indices.cy.ts rename x-pack/test/apm_api_integration/tests/settings/{ => custom_link}/custom_link.spec.ts (51%) diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_configurations.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_configurations.cy.ts index 7da38721eb7f7..d5bb161512a9b 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_configurations.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_configurations.cy.ts @@ -79,55 +79,95 @@ describe('Agent configuration', () => { synthtrace.clean(); }); - beforeEach(() => { - cy.loginAsEditorUser(); - cy.visitKibana(agentConfigHref); + describe('when logged in as viewer user', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana(agentConfigHref); + }); + + it('shows create button as disabled', () => { + cy.contains('Create configuration').should('be.disabled'); + }); }); - it('persists service enviroment when clicking on edit button', () => { - cy.intercept('GET', '/api/apm/settings/agent-configuration/environments?*').as( - 'serviceEnvironmentApi' - ); - cy.contains('Create configuration').click(); - cy.getByTestSubj('serviceNameComboBox').click().type('opbeans-node').type('{enter}'); - - cy.contains('opbeans-node').realClick(); - cy.wait('@serviceEnvironmentApi'); - - cy.getByTestSubj('serviceEnviromentComboBox') - .click({ force: true }) - .type('prod') - .type('{enter}'); - cy.contains('production').realClick(); - cy.contains('Next step').click(); - cy.contains('Create configuration'); - cy.contains('Edit').click(); - cy.wait('@serviceEnvironmentApi'); - cy.getByTestSubj('serviceEnviromentComboBox') - .find('input') - .invoke('val') - .should('contain', 'production'); + describe('when logged in as a viewer with write settings access', () => { + beforeEach(() => { + cy.loginAsApmReadPrivilegesWithWriteSettingsUser(); + cy.visitKibana(agentConfigHref); + }); + + it('shows create button as enabled', () => { + cy.contains('Create configuration').should('not.be.disabled'); + }); }); - it.skip('displays All label when selecting all option', () => { - cy.intercept('GET', '/api/apm/settings/agent-configuration/environments').as( - 'serviceEnvironmentApi' - ); - cy.contains('Create configuration').click(); - cy.getByTestSubj('serviceNameComboBox').click().type('All').type('{enter}'); - cy.contains('All').realClick(); - cy.wait('@serviceEnvironmentApi'); - - cy.getByTestSubj('serviceEnviromentComboBox').click({ force: true }).type('All'); - - cy.get('mark').contains('All').click({ force: true }); - cy.contains('Next step').click(); - cy.get('[data-test-subj="settingsPage_serviceName"]').contains('All'); - cy.get('[data-test-subj="settingsPage_environmentName"]').contains('All'); - cy.contains('Edit').click(); - cy.wait('@serviceEnvironmentApi'); - cy.getByTestSubj('serviceEnviromentComboBox') - .find('input') - .invoke('val') - .should('contain', 'All'); + + describe('when logged in as an editor without write settings access', () => { + beforeEach(() => { + cy.loginAsApmAllPrivilegesWithoutWriteSettingsUser(); + cy.visitKibana(agentConfigHref); + }); + + it('shows create button as disabled', () => { + cy.contains('Create configuration').should('be.disabled'); + }); + }); + + describe('when logged in as editor user', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + cy.visitKibana(agentConfigHref); + }); + + it('shows create button as enabled', () => { + cy.contains('Create configuration').should('not.be.disabled'); + }); + + it('persists service environment when clicking on edit button', () => { + cy.intercept('GET', '/api/apm/settings/agent-configuration/environments?*').as( + 'serviceEnvironmentApi' + ); + cy.contains('Create configuration').click(); + cy.getByTestSubj('serviceNameComboBox').click().type('opbeans-node').type('{enter}'); + + cy.contains('opbeans-node').realClick(); + cy.wait('@serviceEnvironmentApi'); + + cy.getByTestSubj('serviceEnviromentComboBox') + .click({ force: true }) + .type('prod') + .type('{enter}'); + cy.contains('production').realClick(); + cy.contains('Next step').click(); + cy.contains('Create configuration'); + cy.contains('Edit').click(); + cy.wait('@serviceEnvironmentApi'); + cy.getByTestSubj('serviceEnviromentComboBox') + .find('input') + .invoke('val') + .should('contain', 'production'); + }); + + it('displays All label when selecting all option', () => { + cy.intercept('GET', '/api/apm/settings/agent-configuration/environments').as( + 'serviceEnvironmentApi' + ); + cy.contains('Create configuration').click(); + cy.getByTestSubj('serviceNameComboBox').click().type('All').type('{enter}'); + cy.contains('All').realClick(); + cy.wait('@serviceEnvironmentApi'); + + cy.getByTestSubj('serviceEnviromentComboBox').click({ force: true }).type('All'); + + cy.get('mark').contains('All').click({ force: true }); + cy.contains('Next step').click(); + cy.get('[data-test-subj="settingsPage_serviceName"]').contains('All'); + cy.get('[data-test-subj="settingsPage_environmentName"]').contains('All'); + cy.contains('Edit').click(); + cy.wait('@serviceEnvironmentApi'); + cy.getByTestSubj('serviceEnviromentComboBox') + .find('input') + .invoke('val') + .should('contain', 'All'); + }); }); }); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_keys.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_keys.cy.ts new file mode 100644 index 0000000000000..e4c62fa39c426 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/agent_keys.cy.ts @@ -0,0 +1,102 @@ +/* + * 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. + */ + +const basePath = '/app/apm/settings/agent-keys'; + +const deleteAllAgentKeys = () => { + const kibanaUrl = Cypress.env('KIBANA_URL'); + cy.request({ + log: false, + method: 'GET', + url: `${kibanaUrl}/internal/apm/agent_keys`, + body: {}, + headers: { + 'kbn-xsrf': 'e2e_test', + }, + auth: { user: 'elastic', pass: 'changeme' }, + }).then((response) => { + const promises = response.body.agentKeys.map((item: any) => { + if (item.id) { + return cy.request({ + log: false, + method: 'POST', + url: `${kibanaUrl}/internal/apm/api_key/invalidate`, + body: { + id: item.id, + }, + headers: { + 'kbn-xsrf': 'e2e_test', + }, + auth: { user: 'elastic', pass: 'changeme' }, + }); + } + }); + return Promise.all(promises); + }); +}; + +const TEST_AGENT_KEY = 'test-agent-key'; + +const getAbleToModifyCase = () => { + it('should be able to modify settings', () => { + cy.visitKibana(basePath); + const button = cy.get('button[data-test-subj="apmAgentKeysContentCreateApmAgentKeyButton"]'); + button.should('not.be.disabled'); + button.click(); + cy.get('input[data-test-subj="apmCreateAgentKeyFlyoutFieldText"]').type(TEST_AGENT_KEY); + cy.get('button[data-test-subj="apmCreateAgentKeyFlyoutButton"]').click(); + }); +}; + +const getUnableToModifyCase = () => { + it('should not be able to modify settings', () => { + cy.visitKibana(basePath); + const button = cy.get('button[data-test-subj="apmAgentKeysContentCreateApmAgentKeyButton"]'); + button.should('be.disabled'); + }); +}; + +describe('Agent keys', () => { + describe('when logged in as a viewer', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + deleteAllAgentKeys(); + }); + + it('should see missing privileges message', () => { + cy.visitKibana(basePath); + cy.contains('You need permission to manage API keys'); + }); + }); + + describe('when logged in as an editor without write settings access', () => { + beforeEach(() => { + cy.loginAsApmAllPrivilegesWithoutWriteSettingsUser(); + deleteAllAgentKeys(); + }); + + getUnableToModifyCase(); + }); + + describe('when logged in as a superuser', () => { + beforeEach(() => { + cy.loginAsSuperUser(); + deleteAllAgentKeys(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as a viewer with write settings access', () => { + beforeEach(() => { + cy.loginAsApmReadPrivilegesWithWriteSettingsUser(); + deleteAllAgentKeys(); + }); + + getAbleToModifyCase(); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/anomaly_detection.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/anomaly_detection.cy.ts new file mode 100644 index 0000000000000..abdcc36590d75 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/anomaly_detection.cy.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { synthtrace } from '../../../synthtrace'; + +const basePath = '/app/apm/settings/anomaly-detection'; + +const timeRange = { + rangeFrom: '2021-10-10T00:00:00.000Z', + rangeTo: '2021-10-10T00:15:00.000Z', +}; + +function generateData({ + from, + to, + serviceName, + environment, +}: { + from: number; + to: number; + serviceName: string; + environment: string; +}) { + const range = timerange(from, to); + + const service1 = apm + .service({ + name: serviceName, + environment, + agentName: 'java', + }) + .instance('service-1-prod-1') + .podId('service-1-prod-1-pod'); + + return range + .interval('1m') + .rate(1) + .generator((timestamp, index) => [ + service1 + .transaction({ transactionName: 'GET /apple 🍎 ' }) + .timestamp(timestamp) + .duration(1000) + .success(), + ]); +} + +const getAbleToModifyCase = () => { + it('should be able to modify settings', () => { + const { rangeFrom, rangeTo } = timeRange; + const TEST_ENV = 'test environment ' + new Date().toISOString(); + + synthtrace.index( + generateData({ + from: new Date(rangeFrom).getTime(), + to: new Date(rangeTo).getTime(), + serviceName: 'opbeans-node', + environment: TEST_ENV, + }) + ); + + cy.visitKibana(basePath); + const button = cy.get('button[data-test-subj="apmJobsListCreateJobButton"]'); + button.should('not.be.disabled'); + button.click(); + cy.get('div[data-test-subj="comboBoxInput"]').click(); + cy.get(`button[title="${TEST_ENV}"]`).click(); + cy.get('button[data-test-subj="apmAddEnvironmentsCreateJobsButton"]').click(); + cy.intercept('GET', '/internal/apm/settings/anomaly-detection/jobs*').as('internalApiRequest'); + cy.wait('@internalApiRequest'); + cy.contains('Anomaly detection jobs created'); + }); +}; + +const getUnableToModifyCase = () => { + it('should not be able to modify settings', () => { + cy.visitKibana(basePath); + const button = cy.get('button[data-test-subj="apmJobsListCreateJobButton"]'); + button.should('be.disabled'); + }); +}; + +describe('Anomaly detection', () => { + after(() => { + synthtrace.clean(); + }); + + describe('when logged in as a viewer', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + }); + + getUnableToModifyCase(); + }); + + describe('when logged in as an editor', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as a viewer with write settings access', () => { + beforeEach(() => { + cy.loginAsApmReadPrivilegesWithWriteSettingsUser(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as an editor without write settings access', () => { + beforeEach(() => { + cy.loginAsApmAllPrivilegesWithoutWriteSettingsUser(); + }); + + getUnableToModifyCase(); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/custom_links.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/custom_links.cy.ts index bcd56f24c3d84..567820ea6b70b 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/custom_links.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/custom_links.cy.ts @@ -40,49 +40,92 @@ const deleteAllCustomLinks = () => { }; describe('Custom links', () => { - beforeEach(() => { - cy.loginAsEditorUser(); + before(() => { deleteAllCustomLinks(); }); - it('shows empty message and create button', () => { - cy.visitKibana(basePath); - cy.contains('No links found'); - cy.contains('Create custom link'); + describe('when logged in as a viewer with write settings access', () => { + beforeEach(() => { + cy.loginAsApmReadPrivilegesWithWriteSettingsUser(); + }); + + it('shows empty message and create button', () => { + cy.visitKibana(basePath); + cy.contains('No links found'); + cy.contains('Create custom link').should('be.not.disabled'); + }); + + it('creates custom link', () => { + cy.visitKibana(basePath); + const emptyPrompt = cy.getByTestSubj('customLinksEmptyPrompt'); + cy.contains('Create custom link').click(); + cy.contains('Create link'); + cy.contains('Save').should('be.disabled'); + cy.get('input[name="label"]').type('foo'); + cy.get('input[name="url"]').type('https://foo.com'); + cy.contains('Save').should('not.be.disabled'); + cy.contains('Save').click(); + emptyPrompt.should('not.exist'); + cy.contains('foo'); + cy.contains('https://foo.com'); + }); }); - it('creates custom link', () => { - cy.visitKibana(basePath); - const emptyPrompt = cy.getByTestSubj('customLinksEmptyPrompt'); - cy.contains('Create custom link').click(); - cy.contains('Create link'); - cy.contains('Save').should('be.disabled'); - cy.get('input[name="label"]').type('foo'); - cy.get('input[name="url"]').type('https://foo.com'); - cy.contains('Save').should('not.be.disabled'); - cy.contains('Save').click(); - emptyPrompt.should('not.exist'); - cy.contains('foo'); - cy.contains('https://foo.com'); - cy.getByTestSubj('editCustomLink').click(); - cy.contains('Delete').click(); + describe('when logged in as a viewer', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + }); + + it('shows disabled create button and edit button', () => { + cy.visitKibana(basePath); + cy.contains('Create custom link').should('be.disabled'); + cy.getByTestSubj('editCustomLink').should('not.exist'); + }); + }); + + describe('when logged in as an editor without write settings access', () => { + beforeEach(() => { + cy.loginAsApmAllPrivilegesWithoutWriteSettingsUser(); + }); + + it('shows disabled create button and edit button', () => { + cy.visitKibana(basePath); + cy.contains('Create custom link').should('be.disabled'); + cy.getByTestSubj('editCustomLink').should('not.exist'); + }); }); - it('clears filter values when field is selected', () => { - cy.visitKibana(basePath); - - // wait for empty prompt - cy.getByTestSubj('customLinksEmptyPrompt').should('be.visible'); - - cy.contains('Create custom link').click(); - cy.getByTestSubj('filter-0').select('service.name'); - cy.get('[data-test-subj="service.name.value"] [data-test-subj="comboBoxSearchInput"]').type( - 'foo' - ); - cy.getByTestSubj('filter-0').select('service.environment'); - cy.get('[data-test-subj="service.environment.value"] [data-test-subj="comboBoxInput"]').should( - 'not.contain', - 'foo' - ); + describe('when logged in as an editor', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + }); + + it('shows create button', () => { + cy.visitKibana(basePath); + cy.contains('Create custom link').should('not.be.disabled'); + }); + + it('deletes custom link', () => { + cy.visitKibana(basePath); + cy.getByTestSubj('editCustomLink').click(); + cy.contains('Delete').click(); + }); + + it('clears filter values when field is selected', () => { + cy.visitKibana(basePath); + + // wait for empty prompt + cy.getByTestSubj('customLinksEmptyPrompt').should('be.visible'); + + cy.contains('Create custom link').click(); + cy.getByTestSubj('filter-0').select('service.name'); + cy.get('[data-test-subj="service.name.value"] [data-test-subj="comboBoxSearchInput"]').type( + 'foo' + ); + cy.getByTestSubj('filter-0').select('service.environment'); + cy.get( + '[data-test-subj="service.environment.value"] [data-test-subj="comboBoxInput"]' + ).should('not.contain', 'foo'); + }); }); }); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/general_settings.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/general_settings.cy.ts new file mode 100644 index 0000000000000..6217c6db53eb0 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/general_settings.cy.ts @@ -0,0 +1,63 @@ +/* + * 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. + */ + +const basePath = '/app/apm/settings/general-settings'; + +const getAbleToModifyCase = () => { + it('should be able to modify settings', () => { + cy.visitKibana(basePath); + const button = cy.get('button[name="Inspect ES queries"]'); + button.should('not.be.disabled'); + button.click(); + cy.intercept('POST', '/internal/kibana/settings').as('saveSettings'); + cy.contains('Save changes').click(); + cy.wait('@saveSettings').its('response.statusCode').should('eq', 200); + }); +}; + +const getUnableToModifyCase = () => { + it('should not be able to modify settings', () => { + cy.visitKibana(basePath); + const button = cy.get('button[name="Inspect ES queries"]'); + button.should('be.disabled'); + cy.contains('Save changes').should('not.exist'); + }); +}; + +describe('General Settings', () => { + describe('when logged in as a viewer', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + }); + + getUnableToModifyCase(); + }); + + describe('when logged in as an editor', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as a viewer with write settings access', () => { + beforeEach(() => { + cy.loginAsApmReadPrivilegesWithWriteSettingsUser(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as an editor without write settings access', () => { + beforeEach(() => { + cy.loginAsApmAllPrivilegesWithoutWriteSettingsUser(); + }); + + getUnableToModifyCase(); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/indices.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/indices.cy.ts new file mode 100644 index 0000000000000..5b82f13cb3db7 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/settings/indices.cy.ts @@ -0,0 +1,64 @@ +/* + * 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. + */ + +const basePath = '/app/apm/settings/apm-indices'; + +const getAbleToModifyCase = () => { + it('should be able to modify settings', () => { + const newErrorIndex = 'logs-*'; + cy.visitKibana(basePath); + const input = cy.get('input[name="error"]'); + input.should('not.be.disabled'); + input.clear().type(newErrorIndex); + cy.intercept('POST', '/internal/apm/settings/apm-indices/save*').as('internalApiRequest'); + cy.contains('Apply changes').should('not.be.disabled').click(); + cy.wait('@internalApiRequest').its('response.statusCode').should('eq', 200); + }); +}; + +const getUnableToModifyCase = () => { + it('should not be able to modify settings', () => { + cy.visitKibana(basePath); + const input = cy.get('input[name="error"]'); + input.should('be.disabled'); + cy.contains('Apply changes').should('be.disabled'); + }); +}; + +describe('Indices', () => { + describe('when logged in as a viewer', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + }); + + getUnableToModifyCase(); + }); + + describe('when logged in as an editor', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as a viewer with write settings access', () => { + beforeEach(() => { + cy.loginAsApmReadPrivilegesWithWriteSettingsUser(); + }); + + getAbleToModifyCase(); + }); + + describe('when logged in as an editor without write settings access', () => { + beforeEach(() => { + cy.loginAsApmAllPrivilegesWithoutWriteSettingsUser(); + }); + + getUnableToModifyCase(); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts index 2dc2aefc74dbb..404bc5d2492ee 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts @@ -54,11 +54,14 @@ describe('Transaction details', () => { })}` ); - cy.wait([ - '@transactionLatencyRequest', - '@transactionThroughputRequest', - '@transactionFailureRateRequest', - ]).spread((latencyInterception, throughputInterception, failureRateInterception) => { + cy.wait( + [ + '@transactionLatencyRequest', + '@transactionThroughputRequest', + '@transactionFailureRateRequest', + ], + { timeout: 60000 } + ).spread((latencyInterception, throughputInterception, failureRateInterception) => { expect(latencyInterception.request.query.transactionName).to.be.eql('GET /api/product'); expect( diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/commands.ts index 58a2ad006c0a6..d9c0ef08590ce 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/commands.ts @@ -38,6 +38,20 @@ Cypress.Commands.add('loginAsApmManageOwnAndCreateAgentKeys', () => { }); }); +Cypress.Commands.add('loginAsApmAllPrivilegesWithoutWriteSettingsUser', () => { + return cy.loginAs({ + username: ApmUsername.apmAllPrivilegesWithoutWriteSettings, + password: 'changeme', + }); +}); + +Cypress.Commands.add('loginAsApmReadPrivilegesWithWriteSettingsUser', () => { + return cy.loginAs({ + username: ApmUsername.apmReadPrivilegesWithWriteSettings, + password: 'changeme', + }); +}); + Cypress.Commands.add( 'loginAs', ({ username, password }: { username: string; password: string }) => { diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/types.d.ts index 5b40ce38b2c3e..2c5a4ae35f311 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/support/types.d.ts @@ -12,6 +12,8 @@ declare namespace Cypress { loginAsEditorUser(): Cypress.Chainable>; loginAsMonitorUser(): Cypress.Chainable>; loginAsApmManageOwnAndCreateAgentKeys(): Cypress.Chainable>; + loginAsApmAllPrivilegesWithoutWriteSettingsUser(): Cypress.Chainable>; + loginAsApmReadPrivilegesWithWriteSettingsUser(): Cypress.Chainable>; loginAs(params: { username: string; password: string; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/index.tsx index 8969cce42e294..bbcf5582360d2 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/index.tsx @@ -69,7 +69,7 @@ function CreateConfigurationButton() { const { core } = useApmPluginContext(); - const canSave = core.application.capabilities.apm.save; + const canSave = core.application.capabilities.apm['settings:save']; return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/list/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/list/index.tsx index 58616e736f622..ae36d8b0434d7 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/list/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_configurations/list/index.tsx @@ -39,7 +39,7 @@ interface Props { export function AgentConfigurationList({ status, configurations, refetch }: Props) { const { core } = useApmPluginContext(); - const canSave = core.application.capabilities.apm.save; + const canSave = core.application.capabilities.apm['settings:save']; const theme = useTheme(); const [configToBeDeleted, setConfigToBeDeleted] = useState(null); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/agent_keys_table.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/agent_keys_table.tsx index 142ffbd905637..ac013caaa5fba 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/agent_keys_table.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/agent_keys_table.tsx @@ -15,9 +15,10 @@ import { ConfirmDeleteModal } from './confirm_delete_modal'; interface Props { agentKeys: ApiKey[]; onKeyDelete: () => void; + canManage: boolean; } -export function AgentKeysTable({ agentKeys, onKeyDelete }: Props) { +export function AgentKeysTable({ agentKeys, onKeyDelete, canManage }: Props) { const [agentKeyToBeDeleted, setAgentKeyToBeDeleted] = useState(); const columns: Array> = [ @@ -54,7 +55,10 @@ export function AgentKeysTable({ agentKeys, onKeyDelete }: Props) { }, render: (date: number) => , }, - { + ]; + + if (canManage) { + columns.push({ actions: [ { name: i18n.translate('xpack.apm.settings.agentKeys.table.deleteActionTitle', { @@ -72,8 +76,8 @@ export function AgentKeysTable({ agentKeys, onKeyDelete }: Props) { onClick: (agentKey: ApiKey) => setAgentKeyToBeDeleted(agentKey), }, ], - }, - ]; + }); + } const search: EuiInMemoryTableProps['search'] = { box: { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/index.tsx index 960e4cac31663..ef50b81d090b6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/agent_keys/index.tsx @@ -16,6 +16,7 @@ import { EuiEmptyPrompt, EuiButton, EuiLoadingSpinner, + EuiToolTip, } from '@elastic/eui'; import { ApiKey } from '@kbn/security-plugin-types-common'; import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; @@ -33,19 +34,22 @@ const INITIAL_DATA = { }; export function AgentKeys() { - const { toasts } = useApmPluginContext().core.notifications; - + const { core } = useApmPluginContext(); + const { toasts } = core.notifications; + const canSave = core.application.capabilities.apm['settings:save'] as boolean; const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [createdAgentKey, setCreatedAgentKey] = useState(); - const { data: { areApiKeysEnabled, canManage } = INITIAL_DATA, status: privilegesStatus } = - useFetcher( - (callApmApi) => { - return callApmApi('GET /internal/apm/agent_keys/privileges'); - }, - [], - { showToastOnError: false } - ); + const { + data: { areApiKeysEnabled, canManage: canManageAgentKeys } = INITIAL_DATA, + status: privilegesStatus, + } = useFetcher( + (callApmApi) => { + return callApmApi('GET /internal/apm/agent_keys/privileges'); + }, + [], + { showToastOnError: false } + ); const { data, @@ -53,14 +57,15 @@ export function AgentKeys() { refetch: refetchAgentKeys, } = useFetcher( (callApmApi) => { - if (areApiKeysEnabled && canManage) { + if (areApiKeysEnabled && canManageAgentKeys) { return callApmApi('GET /internal/apm/agent_keys'); } }, - [areApiKeysEnabled, canManage], + [areApiKeysEnabled, canManageAgentKeys], { showToastOnError: false } ); + const canManage = canManageAgentKeys && canSave; const agentKeys = data?.agentKeys; return ( @@ -220,23 +225,38 @@ function AgentKeysContent({

} actions={ - - {i18n.translate('xpack.apm.settings.agentKeys.createAgentKeyButton', { - defaultMessage: 'Create APM agent key', - })} - + + {i18n.translate('xpack.apm.settings.agentKeys.createAgentKeyButton', { + defaultMessage: 'Create APM agent key', + })} + + } /> ); } if (agentKeys && !isEmpty(agentKeys)) { - return ; + return ( + + ); } return null; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/anomaly_detection/jobs_list.tsx index 85c740d5cdfe7..399964c98cb96 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/anomaly_detection/jobs_list.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/anomaly_detection/jobs_list.tsx @@ -120,8 +120,8 @@ export function JobsList({ data, status, onAddEnvironments, setupState, onUpdate ); const mlManageJobsHref = useMlManageJobsHref(); - - const displayMlCallout = shouldDisplayMlCallout(setupState); + const canSave = core.application.capabilities.apm['settings:save']; + const displayMlCallout = shouldDisplayMlCallout(setupState) && canSave; const filteredJobs = showLegacyJobs ? jobs : jobs.filter((job) => job.version >= 3); @@ -215,16 +215,29 @@ export function JobsList({ data, status, onAddEnvironments, setupState, onUpdate
- - {i18n.translate('xpack.apm.settings.anomalyDetection.jobList.addEnvironments', { - defaultMessage: 'Create job', - })} - + + {i18n.translate('xpack.apm.settings.anomalyDetection.jobList.addEnvironments', { + defaultMessage: 'Create job', + })} + + diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/apm_indices/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/apm_indices/index.tsx index f75d27295d9e9..1e6a5d2266d22 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/apm_indices/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/apm_indices/index.tsx @@ -85,7 +85,10 @@ export function ApmIndices() { const { services } = useKibana(); const { notifications, application } = core; - const canSave = application.capabilities.apm.save; + + const canSave = + application.capabilities.apm['settings:save'] && + application.capabilities.savedObjectsManagement.edit; const [apmIndices, setApmIndices] = useState>({}); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/create_custom_link_button.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/create_custom_link_button.tsx index 46944eda8cde6..33a1ba62af09f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/create_custom_link_button.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/create_custom_link_button.tsx @@ -13,7 +13,7 @@ import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plug export function CreateCustomLinkButton({ onClick }: { onClick: () => void }) { const { core } = useApmPluginContext(); - const canSave = core.application.capabilities.apm.save; + const canSave = core.application.capabilities.apm['settings:save']; return ( > = [ { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/index.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/index.test.tsx index 913d0f55732cb..b513809ebc44e 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/index.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/custom_link/index.test.tsx @@ -34,7 +34,7 @@ function getMockAPMContext({ canSave }: { canSave: boolean }) { ...mockApmPluginContextValue, core: { ...mockApmPluginContextValue.core, - application: { capabilities: { apm: { save: canSave }, ml: {} } }, + application: { capabilities: { apm: { 'settings:save': canSave }, ml: {} } }, }, } as unknown as ApmPluginContextValue; } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/settings/general_settings/index.tsx index 97abb8ead2e8d..053cba1b1f7a2 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/settings/general_settings/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/settings/general_settings/index.tsx @@ -67,10 +67,14 @@ function getApmSettingsKeys(isProfilingIntegrationEnabled: boolean) { export function GeneralSettings() { const trackApmEvent = useUiTracker({ app: 'apm' }); - const { docLinks, notifications, settings } = useApmPluginContext().core; + const { docLinks, notifications, settings, application } = useApmPluginContext().core; const isProfilingIntegrationEnabled = useApmFeatureFlag( ApmFeatureFlagName.ProfilingIntegrationAvailable ); + + const canSave = + application.capabilities.advancedSettings.save && + (application.capabilities.apm['settings:save'] as boolean); const apmSettingsKeys = getApmSettingsKeys(isProfilingIntegrationEnabled); const { fields, handleFieldChange, unsavedChanges, saveAll, isSaving, cleanUnsavedChanges } = useEditableSettings(apmSettingsKeys); @@ -114,7 +118,7 @@ export function GeneralSettings() { > diff --git a/x-pack/plugins/observability_solution/apm/server/feature.ts b/x-pack/plugins/observability_solution/apm/server/feature.ts index 1932a07b5ebd6..f9b047c602cda 100644 --- a/x-pack/plugins/observability_solution/apm/server/feature.ts +++ b/x-pack/plugins/observability_solution/apm/server/feature.ts @@ -15,12 +15,12 @@ import { import { APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices'; import { ApmRuleType } from '@kbn/rule-data-utils'; -import { KibanaFeatureScope } from '@kbn/features-plugin/common'; +import { KibanaFeatureConfig, KibanaFeatureScope } from '@kbn/features-plugin/common'; import { APM_SERVER_FEATURE_ID } from '../common/rules/apm_rule_types'; const ruleTypes = Object.values(ApmRuleType); -export const APM_FEATURE = { +export const APM_FEATURE: KibanaFeatureConfig = { id: APM_SERVER_FEATURE_ID, name: i18n.translate('xpack.apm.featureRegistry.apmFeatureName', { defaultMessage: 'APM and User Experience', @@ -79,6 +79,33 @@ export const APM_FEATURE = { ui: ['show', 'alerting:show'], }, }, + subFeatures: [ + { + name: i18n.translate('xpack.apm.subFeatureRegistry.settings', { + defaultMessage: 'Settings', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'settings_save', + name: i18n.translate('xpack.apm.subFeatureRegistry.modifySettings', { + defaultMessage: 'Ability to modify settings', + }), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + api: ['apm_settings_write'], + ui: ['settings:save'], + }, + ], + }, + ], + }, + ], }; interface Feature { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts index f0e10eade9aa5..d8c2cd70768c4 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts @@ -55,7 +55,7 @@ const agentKeysPrivilegesRoute = createApmServerRoute({ const invalidateAgentKeyRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/api_key/invalidate', - options: { tags: ['access:apm', 'access:apm_write'] }, + options: { tags: ['access:apm', 'access:apm_settings_write'] }, params: t.type({ body: t.type({ id: t.string }), }), @@ -91,7 +91,7 @@ const invalidateAgentKeyRoute = createApmServerRoute({ const createAgentKeyRoute = createApmServerRoute({ endpoint: 'POST /api/apm/agent_keys 2023-10-31', - options: { tags: ['access:apm', 'access:apm_write', 'oas-tag:APM agent keys'] }, + options: { tags: ['access:apm', 'access:apm_settings_write', 'oas-tag:APM agent keys'] }, params: t.type({ body: t.type({ name: t.string, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.test.ts index 59a3b0c3fa9e7..3475830146634 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.test.ts @@ -159,11 +159,18 @@ describe('createApi', () => { }, handler: async () => ({}), }, + { + endpoint: 'GET /fez', + options: { + tags: ['access:apm', 'access:apm_settings_write'], + }, + handler: async () => ({}), + }, ]); expect(createRouter).toHaveBeenCalledTimes(1); - expect(get).toHaveBeenCalledTimes(2); + expect(get).toHaveBeenCalledTimes(3); expect(post).toHaveBeenCalledTimes(1); expect(put).toHaveBeenCalledTimes(1); @@ -183,6 +190,14 @@ describe('createApi', () => { validate: expect.anything(), }); + expect(get.mock.calls[2][0]).toEqual({ + options: { + tags: ['access:apm', 'access:apm_settings_write'], + }, + path: '/fez', + validate: expect.anything(), + }); + expect(post.mock.calls[0][0]).toEqual({ options: { tags: ['access:apm'], diff --git a/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts index 1b3787e285eef..aaf8fb2c48681 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts @@ -99,7 +99,7 @@ const getSingleAgentConfigurationRoute = createApmServerRoute({ const deleteAgentConfigurationRoute = createApmServerRoute({ endpoint: 'DELETE /api/apm/settings/agent-configuration 2023-10-31', options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_settings_write'], }, params: t.type({ body: t.type({ @@ -155,7 +155,7 @@ const deleteAgentConfigurationRoute = createApmServerRoute({ const createOrUpdateAgentConfigurationRoute = createApmServerRoute({ endpoint: 'PUT /api/apm/settings/agent-configuration 2023-10-31', options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_settings_write'], }, params: t.intersection([ t.partial({ query: t.partial({ overwrite: toBooleanRt }) }), diff --git a/x-pack/plugins/observability_solution/apm/server/routes/settings/anomaly_detection/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/settings/anomaly_detection/route.ts index 5e9ff6676ca8d..00ceee9451f46 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/settings/anomaly_detection/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/settings/anomaly_detection/route.ts @@ -60,7 +60,7 @@ const anomalyDetectionJobsRoute = createApmServerRoute({ const createAnomalyDetectionJobsRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/settings/anomaly-detection/jobs', options: { - tags: ['access:apm', 'access:apm_write', 'access:ml:canCreateJob'], + tags: ['access:apm', 'access:apm_settings_write', 'access:ml:canCreateJob'], }, params: t.type({ body: t.type({ @@ -129,7 +129,7 @@ const anomalyDetectionUpdateToV3Route = createApmServerRoute({ options: { tags: [ 'access:apm', - 'access:apm_write', + 'access:apm_settings_write', 'access:ml:canCreateJob', 'access:ml:canGetJobs', 'access:ml:canCloseJob', diff --git a/x-pack/plugins/observability_solution/apm/server/routes/settings/apm_indices/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/settings/apm_indices/route.ts index 776d0be72bc5d..5d8ac9f04e740 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/settings/apm_indices/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/settings/apm_indices/route.ts @@ -43,7 +43,7 @@ type SaveApmIndicesBodySchema = { const saveApmIndicesRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/settings/apm-indices/save', options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_settings_write'], }, params: t.type({ body: t.partial({ diff --git a/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/route.ts index 36a13cd7575b0..306e23a679765 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/route.ts @@ -75,7 +75,7 @@ const createCustomLinkRoute = createApmServerRoute({ params: t.type({ body: payloadRt, }), - options: { tags: ['access:apm', 'access:apm_write'] }, + options: { tags: ['access:apm', 'access:apm_settings_write'] }, handler: async (resources): Promise => { const { context, params } = resources; const licensingContext = await context.licensing; @@ -105,7 +105,7 @@ const updateCustomLinkRoute = createApmServerRoute({ body: payloadRt, }), options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_settings_write'], }, handler: async (resources): Promise => { const { params, context } = resources; @@ -136,7 +136,7 @@ const deleteCustomLinkRoute = createApmServerRoute({ }), }), options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_settings_write'], }, handler: async (resources): Promise<{ result: string }> => { const { context, params } = resources; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts index 8ee9a3849a6fb..f9ea085a11e6b 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts @@ -52,6 +52,7 @@ export interface APMRouteCreateOptions { tags: Array< | 'access:apm' | 'access:apm_write' + | 'access:apm_settings_write' | 'access:ml:canGetJobs' | 'access:ml:canCreateJob' | 'access:ml:canCloseJob' diff --git a/x-pack/plugins/observability_solution/apm/server/test_helpers/create_apm_users/authentication.ts b/x-pack/plugins/observability_solution/apm/server/test_helpers/create_apm_users/authentication.ts index 9dd09ade02547..2be3e96476c7b 100644 --- a/x-pack/plugins/observability_solution/apm/server/test_helpers/create_apm_users/authentication.ts +++ b/x-pack/plugins/observability_solution/apm/server/test_helpers/create_apm_users/authentication.ts @@ -17,6 +17,8 @@ export enum ApmUsername { apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', apmManageServiceAccount = 'apm_manage_service_account', + apmAllPrivilegesWithoutWriteSettings = 'apm_all_privileges_without_write_settings', + apmReadPrivilegesWithWriteSettings = 'apm_read_privileges_with_write_settings', } export enum ApmCustomRolename { @@ -26,6 +28,8 @@ export enum ApmCustomRolename { apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', apmManageServiceAccount = 'apm_manage_service_account', + apmAllPrivilegesWithoutWriteSettings = 'apm_all_privileges_without_write_settings', + apmReadPrivilegesWithWriteSettings = 'apm_read_privileges_with_write_settings', } export const customRoles = { @@ -95,6 +99,35 @@ export const customRoles = { cluster: ['manage_service_account'], }, }, + [ApmCustomRolename.apmAllPrivilegesWithoutWriteSettings]: { + elasticsearch: { + cluster: ['manage_api_key'], + }, + kibana: [ + { + base: [], + feature: { apm: ['minimal_all'], ml: ['all'] }, + spaces: ['*'], + }, + ], + }, + [ApmCustomRolename.apmReadPrivilegesWithWriteSettings]: { + elasticsearch: { + cluster: ['manage_api_key'], + }, + kibana: [ + { + base: [], + feature: { + apm: ['minimal_read', 'settings_save'], + advancedSettings: ['all'], + ml: ['all'], + savedObjectsManagement: ['all'], + }, + spaces: ['*'], + }, + ], + }, }; export const users: Record< @@ -134,4 +167,12 @@ export const users: Record< builtInRoleNames: ['editor'], customRoleNames: [ApmCustomRolename.apmManageServiceAccount], }, + [ApmUsername.apmAllPrivilegesWithoutWriteSettings]: { + builtInRoleNames: ['viewer'], + customRoleNames: [ApmCustomRolename.apmAllPrivilegesWithoutWriteSettings], + }, + [ApmUsername.apmReadPrivilegesWithWriteSettings]: { + builtInRoleNames: ['viewer'], + customRoleNames: [ApmCustomRolename.apmReadPrivilegesWithWriteSettings], + }, }; diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 23838eda12efb..8c95f39fd6e3e 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -91,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], - apm: ['all', 'read', 'minimal_all', 'minimal_read'], + apm: ['all', 'read', 'minimal_all', 'minimal_read', 'settings_save'], discover: [ 'all', 'read', diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index d204c3ed8345f..41fe1e79b7f12 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -179,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], - apm: ['all', 'read', 'minimal_all', 'minimal_read'], + apm: ['all', 'read', 'minimal_all', 'minimal_read', 'settings_save'], discover: [ 'all', 'read', diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index 8557ddf57c2a2..f46f9476ff2dd 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -52,7 +52,7 @@ async function getApmApiClient({ export type CreateTestConfig = ReturnType; -type ApmApiClientKey = +export type ApmApiClientKey = | 'noAccessUser' | 'readUser' | 'adminUser' @@ -62,7 +62,13 @@ type ApmApiClientKey = | 'manageOwnAgentKeysUser' | 'createAndAllAgentKeysUser' | 'monitorClusterAndIndicesUser' - | 'manageServiceAccount'; + | 'manageServiceAccount' + | 'apmAllPrivilegesWithoutWriteSettingsUser' + | 'apmReadPrivilegesWithWriteSettingsUser'; + +export interface UserApiClient { + user: ApmApiClientKey; +} export type ApmApiClient = Record>>; @@ -184,6 +190,14 @@ export function createTestConfig( kibanaServer, username: ApmUsername.apmManageServiceAccount, }), + apmAllPrivilegesWithoutWriteSettingsUser: await getApmApiClient({ + kibanaServer, + username: ApmUsername.apmAllPrivilegesWithoutWriteSettings, + }), + apmReadPrivilegesWithWriteSettingsUser: await getApmApiClient({ + kibanaServer, + username: ApmUsername.apmReadPrivilegesWithWriteSettings, + }), }; }, ml: MachineLearningAPIProvider, diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts index 98a971610a149..a490c89a32778 100644 --- a/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts @@ -12,6 +12,7 @@ import { omit, orderBy } from 'lodash'; import { AgentConfigurationIntake } from '@kbn/apm-plugin/common/agent_configuration/configuration_types'; import { AgentConfigSearchParams } from '@kbn/apm-plugin/server/routes/settings/agent_configuration/route'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { UserApiClient } from '../../../common/config'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { addAgentConfigEtagMetric } from './add_agent_config_metrics'; @@ -51,9 +52,12 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); } - function createConfiguration(configuration: AgentConfigurationIntake, { user = 'write' } = {}) { + function createConfiguration( + configuration: AgentConfigurationIntake, + { user }: UserApiClient = { user: 'writeUser' } + ) { log.debug('creating configuration', configuration.service); - const supertestClient = user === 'read' ? apmApiClient.readUser : apmApiClient.writeUser; + const supertestClient = apmApiClient[user]; return supertestClient({ endpoint: 'PUT /api/apm/settings/agent-configuration 2023-10-31', @@ -61,9 +65,12 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); } - function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + function updateConfiguration( + config: AgentConfigurationIntake, + { user }: UserApiClient = { user: 'writeUser' } + ) { log.debug('updating configuration', config.service); - const supertestClient = user === 'read' ? apmApiClient.readUser : apmApiClient.writeUser; + const supertestClient = apmApiClient[user]; return supertestClient({ endpoint: 'PUT /api/apm/settings/agent-configuration 2023-10-31', @@ -71,9 +78,12 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); } - function deleteConfiguration({ service }: AgentConfigurationIntake, { user = 'write' } = {}) { + function deleteConfiguration( + { service }: AgentConfigurationIntake, + { user }: UserApiClient = { user: 'writeUser' } + ) { log.debug('deleting configuration', service); - const supertestClient = user === 'read' ? apmApiClient.readUser : apmApiClient.writeUser; + const supertestClient = apmApiClient[user]; return supertestClient({ endpoint: 'DELETE /api/apm/settings/agent-configuration 2023-10-31', @@ -94,40 +104,40 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte } registry.when( - 'agent configuration when no data is loaded', + '[basic] agent configuration when no data is loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state for environments', async () => { + it('[basic] handles the empty state for environments', async () => { const { body } = await getEnvironments('myservice'); expect(body.environments).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); }); - it('handles the empty state for agent name', async () => { + it('[basic] handles the empty state for agent name', async () => { const { body } = await getAgentName('myservice'); expect(body.agentName).to.eql(undefined); }); - describe('as a read-only user', () => { + describe('[basic] as a read-only user', () => { const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; it('does not allow creating config', async () => { - await expectStatusCode(() => createConfiguration(newConfig, { user: 'read' }), 403); + await expectStatusCode(() => createConfiguration(newConfig, { user: 'readUser' }), 403); }); - describe('when a configuration already exists', () => { + describe('[basic] when a configuration already exists', () => { before(async () => createConfiguration(newConfig)); after(async () => deleteConfiguration(newConfig)); - it('does not allow updating the config', async () => { - await expectStatusCode(() => updateConfiguration(newConfig, { user: 'read' }), 403); + it('[basic] does not allow updating the config', async () => { + await expectStatusCode(() => updateConfiguration(newConfig, { user: 'readUser' }), 403); }); - it('does not allow deleting the config', async () => { - await expectStatusCode(() => deleteConfiguration(newConfig, { user: 'read' }), 403); + it('[basic] does not allow deleting the config', async () => { + await expectStatusCode(() => deleteConfiguration(newConfig, { user: 'readUser' }), 403); }); }); }); - describe('when creating one configuration', () => { + describe('[basic] when creating one configuration', () => { const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' }, @@ -138,7 +148,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', }; - it('can create and delete config', async () => { + it('[basic] can create and delete config', async () => { // assert that config does not exist await expectMissing(() => searchConfigurations(searchParams)); @@ -155,18 +165,18 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte await expectMissing(() => searchConfigurations(searchParams)); }); - describe('when a configuration exists', () => { + describe('[basic] when a configuration exists', () => { before(async () => createConfiguration(newConfig)); after(async () => deleteConfiguration(newConfig)); - it('can find the config', async () => { + it('[basic] can find the config', async () => { const { status, body } = await searchConfigurations(searchParams); expect(status).to.equal(200); expect(body._source.service).to.eql({}); expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); }); - it('can list the config', async () => { + it('[basic] can list the config', async () => { const { status, body } = await getAllConfigurations(); expect(status).to.equal(200); @@ -180,7 +190,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte ]); }); - it('can update the config', async () => { + it('[basic] can update the config', async () => { await updateConfiguration({ service: {}, settings: { transaction_sample_rate: '0.85' }, @@ -193,7 +203,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); }); - describe('when creating multiple configurations', () => { + describe('[basic] when creating multiple configurations', () => { const configs = [ { service: {}, @@ -252,7 +262,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }, ]; - it('can list all configs', async () => { + it('[basic] can list all configs', async () => { const { status, body } = await getAllConfigurations(); expect(status).to.equal(200); expect( @@ -292,7 +302,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); for (const agentRequest of agentsRequests) { - it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { + it(`[basic] ${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { const { status, body } = await searchConfigurations({ service: agentRequest.service, etag: 'abc', @@ -304,7 +314,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte } }); - describe('when an agent retrieves a configuration', () => { + describe('[basic] when an agent retrieves a configuration', () => { const config = { service: { name: 'myservice', environment: 'development' }, settings: { transaction_sample_rate: '0.9' }, @@ -326,7 +336,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte await deleteConfiguration(configProduction); }); - it(`should have 'applied_by_agent=false' before supplying etag`, async () => { + it(`[basic] should have 'applied_by_agent=false' before supplying etag`, async () => { const res1 = await searchConfigurations({ service: { name: 'myservice', environment: 'development' }, }); @@ -342,7 +352,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte expect(res2.body._source.applied_by_agent).to.be(false); }); - it(`should have 'applied_by_agent=true' after supplying etag`, async () => { + it(`[basic] should have 'applied_by_agent=true' after supplying etag`, async () => { await searchConfigurations({ service: { name: 'myservice', environment: 'development' }, etag, @@ -359,14 +369,361 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte // wait until `applied_by_agent` has been updated in elasticsearch expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); }); - it(`should have 'applied_by_agent=false' before marking as applied`, async () => { + it(`[basic] should have 'applied_by_agent=false' before marking as applied`, async () => { const res1 = await searchConfigurations({ service: { name: 'myservice', environment: 'production' }, }); expect(res1.body._source.applied_by_agent).to.be(false); }); - it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { + it(`[basic] should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + mark_as_applied_by_agent: true, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + return !!body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + }); + } + ); + + registry.when( + '[trial] agent configuration when no data is loaded', + { config: 'trial', archives: [] }, + () => { + it('[trial] handles the empty state for environments', async () => { + const { body } = await getEnvironments('myservice'); + expect(body.environments).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); + }); + + it('[trial] handles the empty state for agent name', async () => { + const { body } = await getAgentName('myservice'); + expect(body.agentName).to.eql(undefined); + }); + + describe('[trial] as a read-only user', () => { + const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; + it('[trial] does not allow creating config', async () => { + await expectStatusCode(() => createConfiguration(newConfig, { user: 'readUser' }), 403); + }); + + describe('[trial] when a configuration already exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('[trial] does not allow updating the config', async () => { + await expectStatusCode(() => updateConfiguration(newConfig, { user: 'readUser' }), 403); + }); + + it('[trial] does not allow deleting the config', async () => { + await expectStatusCode(() => deleteConfiguration(newConfig, { user: 'readUser' }), 403); + }); + }); + }); + + describe('[trial] as a all privileges without modify settings user', () => { + const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; + it('[trial] does not allow creating config', async () => { + await expectStatusCode( + () => + createConfiguration(newConfig, { user: 'apmAllPrivilegesWithoutWriteSettingsUser' }), + 403 + ); + }); + + describe('[trial] when a configuration already exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('[trial] does not allow updating the config', async () => { + await expectStatusCode( + () => + updateConfiguration(newConfig, { + user: 'apmAllPrivilegesWithoutWriteSettingsUser', + }), + 403 + ); + }); + + it('[trial] does not allow deleting the config', async () => { + await expectStatusCode( + () => + deleteConfiguration(newConfig, { + user: 'apmAllPrivilegesWithoutWriteSettingsUser', + }), + 403 + ); + }); + }); + }); + + describe('[trial] when creating one configuration', () => { + const newConfig = { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + }; + + const searchParams = { + service: { name: 'myservice', environment: 'development' }, + etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', + }; + + it('[trial] can create and delete config', async () => { + // assert that config does not exist + await expectMissing(() => searchConfigurations(searchParams)); + + // create config + await createConfiguration(newConfig); + + // assert that config now exists + await expectExists(() => searchConfigurations(searchParams)); + + // delete config + await deleteConfiguration(newConfig); + + // assert that config was deleted + await expectMissing(() => searchConfigurations(searchParams)); + }); + + it('[trial] can create and delete config as read privileges and modify settings user', async () => { + // assert that config does not exist + await expectMissing(() => searchConfigurations(searchParams)); + + // create config + await createConfiguration(newConfig, { user: 'apmReadPrivilegesWithWriteSettingsUser' }); + + // assert that config now exists + await expectExists(() => searchConfigurations(searchParams)); + + // delete config + await deleteConfiguration(newConfig, { user: 'apmReadPrivilegesWithWriteSettingsUser' }); + + // assert that config was deleted + await expectMissing(() => searchConfigurations(searchParams)); + }); + + describe('[trial] when a configuration exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('[trial] can find the config', async () => { + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); + }); + + it('[trial] can list the config', async () => { + const { status, body } = await getAllConfigurations(); + + expect(status).to.equal(200); + expect(omitTimestamp(body.configurations)).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + applied_by_agent: false, + etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', + }, + ]); + }); + + it('[trial] can update the config', async () => { + await updateConfiguration({ + service: {}, + settings: { transaction_sample_rate: '0.85' }, + }); + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); + }); + }); + }); + + describe('[trial] when creating multiple configurations', () => { + const configs = [ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + }, + ]; + + before(async () => { + await Promise.all(configs.map((config) => createConfiguration(config))); + }); + + after(async () => { + await Promise.all(configs.map((config) => deleteConfiguration(config))); + }); + + const agentsRequests = [ + { + service: { name: 'non_existing_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.3' }, + }, + { + service: { name: 'non_existing_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.4' }, + }, + { + service: { name: 'non_existing_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.5' }, + }, + ]; + + it('[trial] can list all configs', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect( + orderBy(omitTimestamp(body.configurations), ['settings.transaction_sample_rate']) + ).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + applied_by_agent: false, + etag: '0758cb18817de60cca29e07480d472694239c4c3', + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + applied_by_agent: false, + etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + applied_by_agent: false, + etag: 'af4dac62621b6762e6281481d1f7523af1124120', + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + applied_by_agent: false, + etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + applied_by_agent: false, + etag: '4ce40da57e3c71daca704121c784b911ec05ae81', + }, + ]); + }); + + for (const agentRequest of agentsRequests) { + it(`[trial] ${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { + const { status, body } = await searchConfigurations({ + service: agentRequest.service, + etag: 'abc', + }); + + expect(status).to.equal(200); + expect(body._source.settings).to.eql(agentRequest.expectedSettings); + }); + } + }); + + describe('[trial] when an agent retrieves a configuration', () => { + const config = { + service: { name: 'myservice', environment: 'development' }, + settings: { transaction_sample_rate: '0.9' }, + }; + const configProduction = { + service: { name: 'myservice', environment: 'production' }, + settings: { transaction_sample_rate: '0.9' }, + }; + let etag: string; + + before(async () => { + log.debug('creating agent configuration'); + await createConfiguration(config); + await createConfiguration(configProduction); + }); + + after(async () => { + await deleteConfiguration(config); + await deleteConfiguration(configProduction); + }); + + it(`[trial] should have 'applied_by_agent=false' before supplying etag`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + etag = res1.body._source.etag; + + const res2 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + expect(res2.body._source.applied_by_agent).to.be(false); + }); + + it(`[trial] should have 'applied_by_agent=true' after supplying etag`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + return !!body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + it(`[trial] should have 'applied_by_agent=false' before marking as applied`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + }); + it(`[trial] should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { await searchConfigurations({ service: { name: 'myservice', environment: 'production' }, mark_as_applied_by_agent: true, @@ -447,10 +804,51 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); registry.when( - 'agent configuration when data is loaded', + '[basic] agent configuration when data is loaded', { config: 'basic', archives: [archiveName] }, () => { - it('returns the environments, all unconfigured', async () => { + it('[basic] returns the environments, all unconfigured', async () => { + const { body } = await getEnvironments('opbeans-node'); + const { environments } = body; + + expect(environments.map((item: { name: string }) => item.name)).to.contain( + 'ALL_OPTION_VALUE' + ); + + expect( + environments.every( + (item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false + ) + ).to.be(true); + + expectSnapshot(body).toMatchInline(` + Object { + "environments": Array [ + Object { + "alreadyConfigured": false, + "name": "ALL_OPTION_VALUE", + }, + Object { + "alreadyConfigured": false, + "name": "testing", + }, + ], + } + `); + }); + + it('[basic] returns the agent name', async () => { + const { body } = await getAgentName('opbeans-node'); + expect(body.agentName).to.eql('nodejs'); + }); + } + ); + + registry.when( + '[trial] agent configuration when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + it('[trial] returns the environments, all unconfigured', async () => { const { body } = await getEnvironments('opbeans-node'); const { environments } = body; @@ -480,7 +878,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte `); }); - it('returns the agent name', async () => { + it('[trial] returns the agent name', async () => { const { body } = await getAgentName('opbeans-node'); expect(body.agentName).to.eql('nodejs'); }); diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts index 79ccc261f72c1..cc56731fec07b 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts @@ -6,20 +6,23 @@ */ import expect from '@kbn/expect'; +import { ApmApiClientKey, UserApiClient } from '../../../common/config'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { ApmApiError } from '../../../common/apm_api_supertest'; export default function apiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); const apmApiClient = getService('apmApiClient'); - function getJobs() { - return apmApiClient.writeUser({ + const ml = getService('ml'); + + function getJobs({ user }: UserApiClient = { user: 'readUser' }) { + return apmApiClient[user]({ endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`, }); } - function createJobs(environments: string[]) { - return apmApiClient.readUser({ + function createJobs(environments: string[], { user }: UserApiClient = { user: 'readUser' }) { + return apmApiClient[user]({ endpoint: `POST /internal/apm/settings/anomaly-detection/jobs`, params: { body: { environments }, @@ -27,28 +30,42 @@ export default function apiTest({ getService }: FtrProviderContext) { }); } + function deleteJobs(jobIds: string[]) { + return Promise.allSettled(jobIds.map((jobId) => ml.deleteAnomalyDetectionJobES(jobId))); + } + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); + (['readUser', 'apmAllPrivilegesWithoutWriteSettingsUser'] as ApmApiClientKey[]).forEach( + (user) => { + describe(`when ${user} has read access to ML`, () => { + before(async () => { + const res = await getJobs({ user }); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); + }); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - try { - await createJobs(['production', 'staging']); - expect(true).to.be(false); - } catch (e) { - const err = e as ApmApiError; - expect(err.res.status).to.be(403); - } + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs({ user }); + + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + try { + await createJobs(['production', 'staging'], { user }); + expect(true).to.be(false); + } catch (e) { + const err = e as ApmApiError; + expect(err.res.status).to.be(403); + } + }); + }); }); - }); - }); + } + ); }); } diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/update_to_v3.spec.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/update_to_v3.spec.ts index d01b48763119b..25da3afbaa82e 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/update_to_v3.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/update_to_v3.spec.ts @@ -58,7 +58,17 @@ export default function apiTest({ getService }: FtrProviderContext) { }); } + function deleteJobs(jobIds: string[]) { + return Promise.allSettled(jobIds.map((jobId) => ml.deleteAnomalyDetectionJobES(jobId))); + } + registry.when('Updating ML jobs to v3', { config: 'trial', archives: [] }, () => { + before(async () => { + const res = await getJobs(); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); + }); + describe('when there are no v2 jobs', () => { it('returns a 200/true', async () => { const { status, body } = await callUpdateEndpoint(); diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts index bd86ec66a7aab..652da64384dd7 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { countBy } from 'lodash'; +import { ApmApiClientKey, UserApiClient } from '../../../common/config'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { @@ -14,14 +15,14 @@ export default function apiTest({ getService }: FtrProviderContext) { const apmApiClient = getService('apmApiClient'); const ml = getService('ml'); - function getJobs() { - return apmApiClient.writeUser({ + function getJobs({ user }: UserApiClient = { user: 'writeUser' }) { + return apmApiClient[user]({ endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`, }); } - function createJobs(environments: string[]) { - return apmApiClient.writeUser({ + function createJobs(environments: string[], { user }: UserApiClient = { user: 'writeUser' }) { + return apmApiClient[user]({ endpoint: `POST /internal/apm/settings/anomaly-detection/jobs`, params: { body: { environments }, @@ -34,49 +35,59 @@ export default function apiTest({ getService }: FtrProviderContext) { } registry.when('ML jobs', { config: 'trial', archives: [] }, () => { - describe('when user has write access to ML', () => { - after(async () => { - const res = await getJobs(); - const jobIds = res.body.jobs.map((job: any) => job.jobId); - await deleteJobs(jobIds); - }); - - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('creates two jobs', async () => { - await createJobs(['production', 'staging']); + (['writeUser', 'apmReadPrivilegesWithWriteSettingsUser'] as ApmApiClientKey[]).forEach( + (user) => { + describe(`when ${user} has write access to ML`, () => { + before(async () => { + const res = await getJobs({ user }); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); + }); - const { body } = await getJobs(); - expect(body.hasLegacyJobs).to.be(false); - expect(countBy(body.jobs, 'environment')).to.eql({ - production: 1, - staging: 1, + after(async () => { + const res = await getJobs({ user }); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); }); - }); - describe('with existing ML jobs', () => { - before(async () => { - await createJobs(['production', 'staging']); + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs({ user }); + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); }); - it('skips duplicate job creation', async () => { - await createJobs(['production', 'test']); - const { body } = await getJobs(); - expect(countBy(body.jobs, 'environment')).to.eql({ - production: 1, - staging: 1, - test: 1, + describe('when calling create endpoint', () => { + it('creates two jobs', async () => { + await createJobs(['production', 'staging'], { user }); + + const { body } = await getJobs({ user }); + expect(body.hasLegacyJobs).to.be(false); + expect(countBy(body.jobs, 'environment')).to.eql({ + production: 1, + staging: 1, + }); + }); + + describe('with existing ML jobs', () => { + before(async () => { + await createJobs(['production', 'staging'], { user }); + }); + it('skips duplicate job creation', async () => { + await createJobs(['production', 'test'], { user }); + + const { body } = await getJobs({ user }); + expect(countBy(body.jobs, 'environment')).to.eql({ + production: 1, + staging: 1, + test: 1, + }); + }); }); }); }); - }); - }); + } + ); }); } diff --git a/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts b/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts index fa303d33a2945..41bc0448e063e 100644 --- a/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts @@ -11,6 +11,7 @@ import { APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE, } from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices'; import expect from '@kbn/expect'; +import { ApmApiError } from '../../../common/apm_api_supertest'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apmIndicesTests({ getService }: FtrProviderContext) { @@ -31,7 +32,7 @@ export default function apmIndicesTests({ getService }: FtrProviderContext) { } } - registry.when('APM Indices', { config: 'basic', archives: [] }, () => { + registry.when('[basic] APM Indices', { config: 'basic', archives: [] }, () => { beforeEach(async () => { await deleteSavedObject(); }); @@ -39,7 +40,7 @@ export default function apmIndicesTests({ getService }: FtrProviderContext) { await deleteSavedObject(); }); - it('returns APM Indices', async () => { + it('[basic] returns APM Indices', async () => { const response = await apmApiClient.readUser({ endpoint: 'GET /internal/apm/settings/apm-indices', }); @@ -54,7 +55,7 @@ export default function apmIndicesTests({ getService }: FtrProviderContext) { }); }); - it('updates apm indices', async () => { + it('[basic] updates apm indices', async () => { const INDEX_VALUE = 'foo-*'; const writeResponse = await apmApiClient.writeUser({ @@ -73,7 +74,110 @@ export default function apmIndicesTests({ getService }: FtrProviderContext) { expect(readResponse.body.transaction).to.eql(INDEX_VALUE); }); - it('updates apm indices removing legacy sourcemap', async () => { + it('[basic] updates apm indices removing legacy sourcemap', async () => { + const INDEX_VALUE = 'foo-*'; + + const writeResponse = await apmApiClient.writeUser({ + endpoint: 'POST /internal/apm/settings/apm-indices/save', + params: { + body: { sourcemap: 'bar-*', transaction: INDEX_VALUE }, + }, + }); + expect(writeResponse.status).to.be(200); + const savedAPMSavedObject = writeResponse.body + .attributes as Partial; + expect(savedAPMSavedObject.apmIndices?.transaction).to.eql(INDEX_VALUE); + expect(savedAPMSavedObject.apmIndices?.sourcemap).to.eql(undefined); + + const readResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/apm-indices', + }); + expect(readResponse.body.transaction).to.eql(INDEX_VALUE); + expect(readResponse.body.sourcemap).to.eql('apm-*'); + }); + }); + + registry.when('[trial] APM Indices', { config: 'trial', archives: [] }, () => { + // eslint-disable-next-line mocha/no-sibling-hooks + beforeEach(async () => { + await deleteSavedObject(); + }); + // eslint-disable-next-line mocha/no-sibling-hooks + afterEach(async () => { + await deleteSavedObject(); + }); + + it('[trial] returns APM Indices', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/apm-indices', + }); + expect(response.status).to.be(200); + expect(response.body).to.eql({ + transaction: 'traces-apm*,apm-*,traces-*.otel-*', + span: 'traces-apm*,apm-*,traces-*.otel-*', + error: 'logs-apm*,apm-*,logs-*.otel-*', + metric: 'metrics-apm*,apm-*,metrics-*.otel-*', + onboarding: 'apm-*', + sourcemap: 'apm-*', + }); + }); + + it('[trial] updates apm indices', async () => { + const INDEX_VALUE = 'foo-*'; + + const writeResponse = await apmApiClient.writeUser({ + endpoint: 'POST /internal/apm/settings/apm-indices/save', + params: { + body: { transaction: INDEX_VALUE }, + }, + }); + expect(writeResponse.status).to.be(200); + + const readResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/apm-indices', + }); + + expect(readResponse.status).to.be(200); + expect(readResponse.body.transaction).to.eql(INDEX_VALUE); + }); + + it('[trial] updates apm indices as read privileges with modify settings user', async () => { + const INDEX_VALUE = 'foo-*'; + + const writeResponse = await apmApiClient.apmReadPrivilegesWithWriteSettingsUser({ + endpoint: 'POST /internal/apm/settings/apm-indices/save', + params: { + body: { transaction: INDEX_VALUE }, + }, + }); + expect(writeResponse.status).to.be(200); + + const readResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/apm-indices', + }); + + expect(readResponse.status).to.be(200); + expect(readResponse.body.transaction).to.eql(INDEX_VALUE); + }); + + it('[trial] fails to update apm indices as all privilege without modify settings', async () => { + const INDEX_VALUE = 'foo-*'; + + try { + await apmApiClient.apmAllPrivilegesWithoutWriteSettingsUser({ + endpoint: 'POST /internal/apm/settings/apm-indices/save', + params: { + body: { transaction: INDEX_VALUE }, + }, + }); + expect(true).to.be(false); + } catch (e) { + const err = e as ApmApiError; + expect(err.res.status).to.be(403); + } + }); + + it('[trial] updates apm indices removing legacy sourcemap', async () => { const INDEX_VALUE = 'foo-*'; const writeResponse = await apmApiClient.writeUser({ diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.spec.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts similarity index 51% rename from x-pack/test/apm_api_integration/tests/settings/custom_link.spec.ts rename to x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts index 32cae36a49443..490a2fc768688 100644 --- a/x-pack/test/apm_api_integration/tests/settings/custom_link.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts @@ -7,8 +7,9 @@ import expect from '@kbn/expect'; import { CustomLink } from '@kbn/apm-plugin/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ApmApiError } from '../../common/apm_api_supertest'; +import { ApmApiClientKey, UserApiClient } from '../../../common/config'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { ApmApiError } from '../../../common/apm_api_supertest'; export default function customLinksTests({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -56,76 +57,113 @@ export default function customLinksTests({ getService }: FtrProviderContext) { await createCustomLink(customLink); }); - it('fetches a custom link', async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - const { label, url, filters } = body.customLinks[0]; - - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'with filters', + it('should fail if the user does not have write access', async () => { + const customLink = { url: 'https://elastic.co', + label: 'with filters', filters: [ { key: 'service.name', value: 'baz' }, { key: 'transaction.type', value: 'qux' }, ], - }); + } as CustomLink; + + const err = await expectToReject(() => + createCustomLink(customLink, { user: 'apmAllPrivilegesWithoutWriteSettingsUser' }) + ); + expect(err.res.status).to.be(403); }); - it('updates a custom link', async () => { + it('fetches a custom link', async () => { const { status, body } = await searchCustomLinks({ 'service.name': 'baz', 'transaction.type': 'qux', }); - expect(status).to.equal(200); - - const id = body.customLinks[0].id!; - await updateCustomLink(id, { - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - - const { status: newStatus, body: newBody } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); + const { label, url, filters } = body.customLinks[0]; - const { label, url, filters } = newBody.customLinks[0]; - expect(newStatus).to.equal(200); + expect(status).to.equal(200); expect({ label, url, filters }).to.eql({ - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', + label: 'with filters', + url: 'https://elastic.co', filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, ], }); }); - it('deletes a custom link', async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(status).to.equal(200); - expect(body.customLinks.length).to.be(1); - - const id = body.customLinks[0].id!; - await deleteCustomLink(id); - - const { status: newStatus, body: newBody } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(newStatus).to.equal(200); - expect(newBody.customLinks.length).to.be(0); - }); + (['writeUser', 'apmReadPrivilegesWithWriteSettingsUser'] as ApmApiClientKey[]).forEach( + (user) => { + it(`creates a custom link as ${user}`, async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + + await createCustomLink(customLink, { user }); + }); + + it(`updates a custom link as ${user}`, async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + expect(status).to.equal(200); + + const id = body.customLinks[0].id!; + await updateCustomLink( + id, + { + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }, + { user } + ); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + + const { label, url, filters } = newBody.customLinks[0]; + expect(newStatus).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + }); + + it(`deletes a custom link as ${user}`, async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(status).to.equal(200); + expect(body.customLinks.length).to.be(1); + + const id = body.customLinks[0].id!; + await deleteCustomLink(id, { user }); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(newStatus).to.equal(200); + expect(newBody.customLinks.length).to.be(0); + }); + } + ); it('fetches a transaction sample', async () => { const response = await apmApiClient.readUser({ @@ -151,10 +189,13 @@ export default function customLinksTests({ getService }: FtrProviderContext) { }); } - async function createCustomLink(customLink: CustomLink) { + async function createCustomLink( + customLink: CustomLink, + { user }: UserApiClient = { user: 'writeUser' } + ) { log.debug('creating configuration', customLink); - return apmApiClient.writeUser({ + return apmApiClient[user]({ endpoint: 'POST /internal/apm/settings/custom_links', params: { body: customLink, @@ -162,10 +203,14 @@ export default function customLinksTests({ getService }: FtrProviderContext) { }); } - async function updateCustomLink(id: string, customLink: CustomLink) { + async function updateCustomLink( + id: string, + customLink: CustomLink, + { user }: UserApiClient = { user: 'writeUser' } + ) { log.debug('updating configuration', id, customLink); - return apmApiClient.writeUser({ + return apmApiClient[user]({ endpoint: 'PUT /internal/apm/settings/custom_links/{id}', params: { path: { id }, @@ -174,10 +219,10 @@ export default function customLinksTests({ getService }: FtrProviderContext) { }); } - async function deleteCustomLink(id: string) { + async function deleteCustomLink(id: string, { user }: UserApiClient = { user: 'writeUser' }) { log.debug('deleting configuration', id); - return apmApiClient.writeUser({ + return apmApiClient[user]({ endpoint: 'DELETE /internal/apm/settings/custom_links/{id}', params: { path: { id } }, }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts index ac53ceacf9a0a..885a34572c1f3 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts @@ -52,6 +52,7 @@ export default function ({ getService }: FtrProviderContext) { "api:apm", "api:apm_write", "api:rac", + "api:apm_settings_write", "app:apm", "app:ux", "app:kibana", @@ -96,6 +97,7 @@ export default function ({ getService }: FtrProviderContext) { "ui:apm/save", "ui:apm/alerting:show", "ui:apm/alerting:save", + "ui:apm/settings:save", "alerting:apm.error_rate/apm/rule/get", "alerting:apm.error_rate/apm/rule/getRuleState", "alerting:apm.error_rate/apm/rule/getAlertSummary", @@ -1921,6 +1923,11 @@ export default function ({ getService }: FtrProviderContext) { "alerting:apm.anomaly/observability/alert/getAuthorizedAlertsIndices", "alerting:apm.anomaly/observability/alert/getAlertSummary", ], + "settings_save": Array [ + "login:", + "api:apm_settings_write", + "ui:apm/settings:save", + ], }, "dashboard": Object { "all": Array [ From 72f3d2d3491f6da4b0c9147e766635e9dbb9cbe8 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 15 Oct 2024 16:09:42 +0200 Subject: [PATCH 31/84] [Http] Make HTTP resource routes respond without the versioned header (#195940) ## Summary Follow up on https://github.com/elastic/kibana/pull/195464 Adds public route registrar option `httpResource` to distinguish API routes from routes intended to be used for loading resources, [for ex](https://github.com/elastic/kibana/blob/bd22f1370fc55179ea6f2737176570176f700b6e/x-pack/plugins/security/server/routes/authentication/oidc.ts#L36). This enables us to avoid returning the version header `elastic-api-version` for HTTP resource routes. It's still possible for API authors to use the versioned router for things that should be HTTP resources, but it's assumed that all routes registered through HTTP resources services are: 1. Public 2. Not versioned (focus of this PR) 3. Not documented (done in https://github.com/elastic/kibana/pull/192675) ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/bundle_routes/bundle_route.test.ts | 1 + .../src/bundle_routes/bundles_route.ts | 1 + .../src/http_resources_service.test.ts | 19 ++++ .../src/http_resources_service.ts | 1 + .../src/router.test.ts | 96 +++++++++++++------ .../src/router.ts | 20 ++-- .../core_versioned_route.test.ts | 3 + .../http/core-http-server/src/router/route.ts | 14 +++ .../src/routes/translations.test.ts | 4 +- .../src/routes/translations.ts | 1 + .../http/versioned_router.test.ts | 18 ++++ .../http_resources_service.test.ts | 15 +++ 12 files changed, 153 insertions(+), 40 deletions(-) diff --git a/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.ts index 0b1a0136fea93..e100fe3476ddc 100644 --- a/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.ts @@ -45,6 +45,7 @@ describe('registerRouteForBundle', () => { options: { access: 'public', authRequired: false, + httpResource: true, }, validate: expect.any(Object), }, diff --git a/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts index 08daf6b96e8bf..7ad9c2ef22232 100644 --- a/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts @@ -32,6 +32,7 @@ export function registerRouteForBundle( { path: `${routePath}{path*}`, options: { + httpResource: true, authRequired: false, access: 'public', }, diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts index 1a7757d4e1eaa..2dea4759c3d4b 100644 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts @@ -61,6 +61,25 @@ describe('HttpResources service', () => { expect(registeredRouteConfig.options?.access).toBe('public'); }); + it('registration does not allow changing "httpResource"', () => { + register( + { ...routeConfig, options: { ...routeConfig.options, httpResource: undefined } }, + async (ctx, req, res) => res.ok() + ); + register( + { ...routeConfig, options: { ...routeConfig.options, httpResource: true } }, + async (ctx, req, res) => res.ok() + ); + register( + { ...routeConfig, options: { ...routeConfig.options, httpResource: false } }, + async (ctx, req, res) => res.ok() + ); + const [[first], [second], [third]] = router.get.mock.calls; + expect(first.options?.httpResource).toBe(true); + expect(second.options?.httpResource).toBe(true); + expect(third.options?.httpResource).toBe(true); + }); + it('registration can set access to "internal"', () => { register({ ...routeConfig, options: { access: 'internal' } }, async (ctx, req, res) => res.ok() diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts index 29114c0dffc07..0394977906580 100644 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts @@ -91,6 +91,7 @@ export class HttpResourcesService implements CoreService { diff --git a/packages/core/http/core-http-router-server-internal/src/router.test.ts b/packages/core/http/core-http-router-server-internal/src/router.test.ts index b506933574d4a..c318e9312546a 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.test.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.test.ts @@ -134,40 +134,76 @@ describe('Router', () => { } ); - it('adds versioned header v2023-10-31 to public, unversioned routes', async () => { - const router = new Router('', logger, enhanceWithContext, routerOptions); - router.post( - { - path: '/public', - options: { - access: 'public', + describe('elastic-api-version header', () => { + it('adds the header to public, unversioned routes', async () => { + const router = new Router('', logger, enhanceWithContext, routerOptions); + router.post( + { + path: '/public', + options: { + access: 'public', + }, + validate: false, }, - validate: false, - }, - (context, req, res) => res.ok({ headers: { AAAA: 'test' } }) // with some fake headers - ); - router.post( - { - path: '/internal', - options: { - access: 'internal', + (context, req, res) => res.ok({ headers: { AAAA: 'test' } }) // with some fake headers + ); + router.post( + { + path: '/internal', + options: { + access: 'internal', + }, + validate: false, }, - validate: false, - }, - (context, req, res) => res.ok() - ); - const [{ handler: publicHandler }, { handler: internalHandler }] = router.getRoutes(); + (context, req, res) => res.ok() + ); + const [{ handler: publicHandler }, { handler: internalHandler }] = router.getRoutes(); + + await publicHandler(createRequestMock(), mockResponseToolkit); + expect(mockResponse.header).toHaveBeenCalledTimes(2); + const [first, second] = mockResponse.header.mock.calls + .concat() + .sort(([k1], [k2]) => k1.localeCompare(k2)); + expect(first).toEqual(['AAAA', 'test']); + expect(second).toEqual(['elastic-api-version', '2023-10-31']); + + await internalHandler(createRequestMock(), mockResponseToolkit); + expect(mockResponse.header).toHaveBeenCalledTimes(2); // no additional calls + }); + + it('does not add the header to public http resource routes', async () => { + const router = new Router('', logger, enhanceWithContext, routerOptions); + router.post( + { + path: '/public', + options: { + access: 'public', + }, + validate: false, + }, + (context, req, res) => res.ok() + ); + router.post( + { + path: '/public-resource', + options: { + access: 'public', + httpResource: true, + }, + validate: false, + }, + (context, req, res) => res.ok() + ); + const [{ handler: publicHandler }, { handler: resourceHandler }] = router.getRoutes(); - await publicHandler(createRequestMock(), mockResponseToolkit); - expect(mockResponse.header).toHaveBeenCalledTimes(2); - const [first, second] = mockResponse.header.mock.calls - .concat() - .sort(([k1], [k2]) => k1.localeCompare(k2)); - expect(first).toEqual(['AAAA', 'test']); - expect(second).toEqual(['elastic-api-version', '2023-10-31']); + await publicHandler(createRequestMock(), mockResponseToolkit); + expect(mockResponse.header).toHaveBeenCalledTimes(1); + const [headersTuple] = mockResponse.header.mock.calls; + expect(headersTuple).toEqual(['elastic-api-version', '2023-10-31']); - await internalHandler(createRequestMock(), mockResponseToolkit); - expect(mockResponse.header).toHaveBeenCalledTimes(2); // no additional calls + await resourceHandler(createRequestMock(), mockResponseToolkit); + expect(mockResponse.header).toHaveBeenCalledTimes(1); // no additional calls + }); }); it('constructs lazily provided validations once (idempotency)', async () => { diff --git a/packages/core/http/core-http-router-server-internal/src/router.ts b/packages/core/http/core-http-router-server-internal/src/router.ts index bb99de64581be..36f324236a4d2 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.ts @@ -149,6 +149,7 @@ export interface RouterOptions { export interface InternalRegistrarOptions { isVersioned: boolean; } + /** @internal */ export type VersionedRouteConfig = Omit< RouteConfig, @@ -201,11 +202,15 @@ export class Router( route: InternalRouteConfig, handler: RequestHandler, - { isVersioned }: { isVersioned: boolean } = { isVersioned: false } + { isVersioned }: InternalRegistrarOptions = { isVersioned: false } ) => { route = prepareRouteConfigValidation(route); const routeSchemas = routeSchemasFromRouteConfig(route, method); - const isPublicUnversionedRoute = route.options?.access === 'public' && !isVersioned; + const isPublicUnversionedApi = + !isVersioned && + route.options?.access === 'public' && + // We do not consider HTTP resource routes as APIs + route.options?.httpResource !== true; this.routes.push({ handler: async (req, responseToolkit) => @@ -213,7 +218,7 @@ export class Router, route.options), - /** Below is added for introspection */ validationSchemas: route.validate, isVersioned, }); @@ -269,12 +273,12 @@ export class Router { tags: ['access:test'], timeout: { payload: 60_000, idleSocket: 10_000 }, xsrfRequired: false, + excludeFromOAS: true, + httpResource: true, + summary: `test`, }, }; diff --git a/packages/core/http/core-http-server/src/router/route.ts b/packages/core/http/core-http-server/src/router/route.ts index 194191e6f423f..a97ff9dd4040b 100644 --- a/packages/core/http/core-http-server/src/router/route.ts +++ b/packages/core/http/core-http-server/src/router/route.ts @@ -307,6 +307,20 @@ export interface RouteConfigOptions { * @remarks This will be surfaced in OAS documentation. */ security?: RouteSecurity; + + /** + * Whether this endpoint is being used to serve generated or static HTTP resources + * like JS, CSS or HTML. _Do not set to `true` for HTTP APIs._ + * + * @note Unless you need this setting for a special case, rather use the + * {@link HttpResources} service exposed to plugins directly. + * + * @note This is not a security feature. It may affect aspects of the HTTP + * response like headers. + * + * @default false + */ + httpResource?: boolean; } /** diff --git a/packages/core/i18n/core-i18n-server-internal/src/routes/translations.test.ts b/packages/core/i18n/core-i18n-server-internal/src/routes/translations.test.ts index cd945dc8202f2..6c68388cd6a76 100644 --- a/packages/core/i18n/core-i18n-server-internal/src/routes/translations.test.ts +++ b/packages/core/i18n/core-i18n-server-internal/src/routes/translations.test.ts @@ -24,7 +24,7 @@ describe('registerTranslationsRoute', () => { 1, expect.objectContaining({ path: '/translations/{locale}.json', - options: { access: 'public', authRequired: false }, + options: { access: 'public', authRequired: false, httpResource: true }, }), expect.any(Function) ); @@ -32,7 +32,7 @@ describe('registerTranslationsRoute', () => { 2, expect.objectContaining({ path: '/translations/XXXX/{locale}.json', - options: { access: 'public', authRequired: false }, + options: { access: 'public', authRequired: false, httpResource: true }, }), expect.any(Function) ); diff --git a/packages/core/i18n/core-i18n-server-internal/src/routes/translations.ts b/packages/core/i18n/core-i18n-server-internal/src/routes/translations.ts index 2ffa82cb7baf7..8c4ca28ac59f7 100644 --- a/packages/core/i18n/core-i18n-server-internal/src/routes/translations.ts +++ b/packages/core/i18n/core-i18n-server-internal/src/routes/translations.ts @@ -45,6 +45,7 @@ export const registerTranslationsRoute = ({ }, options: { access: 'public', + httpResource: true, authRequired: false, }, }, diff --git a/src/core/server/integration_tests/http/versioned_router.test.ts b/src/core/server/integration_tests/http/versioned_router.test.ts index 254337f82abcf..7cfa3b78b7013 100644 --- a/src/core/server/integration_tests/http/versioned_router.test.ts +++ b/src/core/server/integration_tests/http/versioned_router.test.ts @@ -188,6 +188,24 @@ describe('Routing versioned requests', () => { ).resolves.toMatchObject({ 'elastic-api-version': '2023-10-31' }); }); + it('returns the version in response headers, even for HTTP resources', async () => { + router.versioned + .get({ path: '/my-path', access: 'public', options: { httpResource: true } }) + .addVersion({ validate: false, version: '2023-10-31' }, async (ctx, req, res) => { + return res.ok({ body: { foo: 'bar' } }); + }); + + await server.start(); + + await expect( + supertest + .get('/my-path') + .set('Elastic-Api-Version', '2023-10-31') + .expect(200) + .then(({ header }) => header) + ).resolves.toMatchObject({ 'elastic-api-version': '2023-10-31' }); + }); + it('runs response validation when in dev', async () => { router.versioned .get({ path: '/my-path', access: 'internal' }) diff --git a/src/core/server/integration_tests/http_resources/http_resources_service.test.ts b/src/core/server/integration_tests/http_resources/http_resources_service.test.ts index 99c29a41e7704..b1ae073de48c8 100644 --- a/src/core/server/integration_tests/http_resources/http_resources_service.test.ts +++ b/src/core/server/integration_tests/http_resources/http_resources_service.test.ts @@ -199,5 +199,20 @@ function applyTestsWithDisableUnsafeEvalSetTo(disableUnsafeEval: boolean) { expect(response.text).toBe('window.alert(42);'); }); }); + + it('responses do not contain the elastic-api-version header', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + const htmlBody = `

HtMlr00lz

`; + resources.register({ path: '/render-html', validate: false }, (context, req, res) => + res.renderHtml({ body: htmlBody }) + ); + + await root.start(); + const { header } = await request.get(root, '/render-html').expect(200); + expect(header).not.toMatchObject({ 'elastic-api-version': expect.any(String) }); + }); }); } From 2c876e8010ce2785c784879217ee2a47cb48e7b0 Mon Sep 17 00:00:00 2001 From: Tre Date: Tue, 15 Oct 2024 15:15:12 +0100 Subject: [PATCH 32/84] [SKIP ON MKI][DA] Deployment Agnostic api integration `burn_rate_rule.ts` (#196259) ## Summary see details: https://github.com/elastic/kibana/issues/196252 --- .../apis/observability/alerting/burn_rate_rule.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts index e556db2e09a28..250fdb07b7132 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts @@ -23,7 +23,9 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const isServerless = config.get('serverless'); const expectedConsumer = isServerless ? 'observability' : 'slo'; - describe('Burn rate rule', () => { + describe('Burn rate rule', function () { + // see details: https://github.com/elastic/kibana/issues/196252 + this.tags(['failsOnMKI']); const RULE_TYPE_ID = 'slo.rules.burnRate'; const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; const RULE_ALERT_INDEX = '.alerts-observability.slo.alerts-default'; From 907de2495df38c399d0dc979e8ab4118c8c810c2 Mon Sep 17 00:00:00 2001 From: Emma Raffenne <97166868+emma-raffenne@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:20:44 +0200 Subject: [PATCH 33/84] Aligning wording across solutions for the custom system prompt (#196088) ## Summary Aligning wording of the custom system prompt with Security solution --- .../knowledge_base_edit_user_instruction_flyout.tsx | 4 ++-- .../public/routes/components/knowledge_base_tab.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_user_instruction_flyout.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_user_instruction_flyout.tsx index 8b12f842bf128..e8152738e3807 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_user_instruction_flyout.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_user_instruction_flyout.tsx @@ -58,7 +58,7 @@ export function KnowledgeBaseEditUserInstructionFlyout({ onClose }: { onClose: (

{i18n.translate( 'xpack.observabilityAiAssistantManagement.knowledgeBaseEditSystemPrompt.h2.editEntryLabel', - { defaultMessage: 'AI User Profile' } + { defaultMessage: 'User-specific System Prompt' } )}

@@ -70,7 +70,7 @@ export function KnowledgeBaseEditUserInstructionFlyout({ onClose }: { onClose: ( 'xpack.observabilityAiAssistantManagement.knowledgeBaseEditSystemPromptFlyout.personalPromptTextLabel', { defaultMessage: - 'The AI User Profile will be appended to the system prompt. It is space-aware and will only be used for your prompts - not shared with other users.', + 'This user-specific prompt will be appended to the system prompt. It is space-aware and will only be used for your prompts - not shared with other users.', } )} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_tab.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_tab.tsx index de27f26f2561c..6ba09101b6227 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_tab.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/knowledge_base_tab.tsx @@ -256,7 +256,7 @@ export function KnowledgeBaseTab() { > {i18n.translate( 'xpack.observabilityAiAssistantManagement.knowledgeBaseTab.editInstructionsButtonLabel', - { defaultMessage: 'Edit AI User Profile' } + { defaultMessage: 'Edit User-specific Prompt' } )}
From 8abe25970aa1b483676dde17b7972359c8c55475 Mon Sep 17 00:00:00 2001 From: Ilya Nikokoshev Date: Tue, 15 Oct 2024 17:24:41 +0300 Subject: [PATCH 34/84] [Auto Import] Fix cases where LLM generates incorrect array field access (#196207) ## Release Note Fixes cases where LLM was likely to generate invalid processors containing array access in Automatic Import. ## Context Previously, it happened from time to time that the LLM attempts to add related fields or apply categorization conditions that use a field, path to which goes through an array. The problem is that such an access is invalid and leads to an immediate error (key part highlighted): Even including explicit instructions to avoid brackets or an array access did not seem enough, as the LLM would try to use a different syntax, owing to the aggressiveness of our review instructions. The suggested solution is to remove all arrays from the information shown to the LLM in the related chain. This guarantees that no illegal access will ever be attempted. ### Summary - Introduces a utility function to remove all arrays from a JSON object. - Applies this function for all LLM calls in the related chain. - Modifies the prompts of related and categorization chain to skip the arrays as well. --------- Co-authored-by: Bharat Pasupula <123897612+bhapas@users.noreply.github.com> --- .../server/graphs/categorization/prompts.ts | 3 + .../server/graphs/related/prompts.ts | 6 +- .../server/graphs/related/related.ts | 3 +- .../server/graphs/related/review.ts | 3 +- .../server/graphs/related/util.test.ts | 135 ++++++++++++++++++ .../server/graphs/related/util.ts | 37 +++++ 6 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/integration_assistant/server/graphs/related/util.test.ts create mode 100644 x-pack/plugins/integration_assistant/server/graphs/related/util.ts diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts index 2f90e426dc552..baf9d5d5b3ada 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts @@ -53,6 +53,7 @@ You ALWAYS follow these guidelines when writing your response: - You can add as many processor objects as you need to cover all the unique combinations that was detected. - If conditions should always use a ? character when accessing nested fields, in case the field might not always be available, see example processors above. +- You can access nested dictionaries with the ctx.field?.another_field syntax, but it's not possible to access elements of an array. Never use brackets in an if statement. - When an if condition is not needed the argument it should not be included in that specific object of your response. - When using a range based if condition like > 0, you first need to check that the field is not null, for example: ctx.somefield?.production != null && ctx.somefield?.production > 0 - If no good match is found for any of the pipeline results, then respond with an empty array [] as valid JSON enclosed with 3 backticks (\`). @@ -110,6 +111,7 @@ You ALWAYS follow these guidelines when writing your response: - You can use as many processor objects as you need to add all relevant ECS categories and types combinations. - If conditions should always use a ? character when accessing nested fields, in case the field might not always be available, see example processors above. +- You can access nested dictionaries with the ctx.field?.another_field syntax, but it's not possible to access elements of an array. Never use brackets in an if statement. - When an if condition is not needed the argument should not be used for the processor object. - If updates are needed you respond with the initially provided array of processors. - If an update removes the last remaining processor object you respond with an empty array [] as valid JSON enclosed with 3 backticks (\`). @@ -159,6 +161,7 @@ You ALWAYS follow these guidelines when writing your response: - If the error complains about having event.type or event.category not in the allowed values , fix the corresponding append processors to use the allowed values mentioned in the error. - If the error is about event.type not compatible with any event.category, please refer to the 'compatible_types' in the context to fix the corresponding append processors to use valid combination of event.type and event.category - If resolving the validation removes the last remaining processor object, respond with an empty array [] as valid JSON enclosed with 3 backticks (\`). +- Reminder: you can access nested dictionaries with the ctx.field?.another_field syntax, but it's not possible to access elements of an array. Never use brackets in an if statement. - Do not respond with anything except the complete updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts index 9fa50d5900806..9b76ddb96ba8d 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts @@ -27,7 +27,7 @@ Here are some context for you to reference for your task, read it carefully as y For each pipeline result you find matching values that would fit any of the related fields perform the follow steps: 1. Identify which related field the value would fit in. -2. Create a new processor object with the field value set to the correct related.field, and the value_field set to the full path of the field that contains the value which we want to append. +2. Create a new processor object with the field value set to the correct related.field, and the value_field set to the full path of the field that contains the value which we want to append, if that path can be encoded as a string of dict key accesses. 3. Always check if the related.ip, related.hash, related.user and related.host fields are common in the ecs context above. 4. The value_field argument in your response consist of only one value. @@ -35,6 +35,7 @@ You ALWAYS follow these guidelines when writing your response: - The \`message\` field may not be part of related fields. - You can use as many processor objects as needed to map all relevant pipeline result fields to any of the ECS related fields. +- You can access nested dictionaries with the field.another_field syntax, but it's not possible to access elements of an array; skip them instead. - If no relevant fields or values are found that could be mapped confidently to any of the related fields, then respond with an empty array [] as valid JSON enclosed with 3 backticks (\`). - Do not respond with anything except the array of processors as a valid JSON objects enclosed with 3 backticks (\`), see example response below. @@ -82,6 +83,7 @@ You ALWAYS follow these guidelines when writing your response: - The \`message\` field may not be part of related fields. - Never use "split" in template values, only use the field name inside the triple brackets. If the error mentions "Improperly closed variable in query-template" then check each "value" field for any special characters and remove them. +- You can access nested dictionaries with the field.another_field syntax, but it's not possible to access elements of an array. Never use brackets in the field name, never try to access array elements. - If solving an error means removing the last processor in the list, then return an empty array [] as valid JSON enclosed with 3 backticks (\`). - Do not respond with anything except the complete updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. @@ -123,7 +125,7 @@ Please review the pipeline results and the array of current processors above, an For each pipeline result you find matching values that would fit any of the related fields perform the follow steps: 1. Identify which related field the value would fit in. -2. Create a new processor object with the field value set to the correct related.field, and the value_field set to the full path of the field that contains the value which we want to append. +2. Create a new processor object with the field value set to the correct related.field, and the value_field set to the full path of the field that contains the value which we want to append. You can access fields inside nested dictionaries with the field.another_field syntax, but it's not possible to access elements of an array, so skip a field if it's path contains an array. 3. If previous errors above is not empty, do not add any processors that would cause any of the same errors again, if you are unsure, then remove the processor from the list. 4. If no updates are needed, then respond with the initially provided current processors. diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/related.ts b/x-pack/plugins/integration_assistant/server/graphs/related/related.ts index 902427a1c484f..4298fb1ab24fa 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/related/related.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/related/related.ts @@ -11,6 +11,7 @@ import type { RelatedState, SimplifiedProcessor, SimplifiedProcessors } from '.. import { combineProcessors } from '../../util/processors'; import { RELATED_MAIN_PROMPT } from './prompts'; import type { RelatedNodeParams } from './types'; +import { deepCopySkipArrays } from './util'; export async function handleRelated({ state, @@ -21,7 +22,7 @@ export async function handleRelated({ const relatedMainGraph = relatedMainPrompt.pipe(model).pipe(outputParser); const currentProcessors = (await relatedMainGraph.invoke({ - pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + pipeline_results: JSON.stringify(state.pipelineResults.map(deepCopySkipArrays), null, 2), ex_answer: state.exAnswer, ecs: state.ecs, })) as SimplifiedProcessor[]; diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/review.ts b/x-pack/plugins/integration_assistant/server/graphs/related/review.ts index 300f33144b52a..37c0008304958 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/related/review.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/related/review.ts @@ -11,6 +11,7 @@ import type { RelatedState, SimplifiedProcessors, SimplifiedProcessor } from '.. import type { RelatedNodeParams } from './types'; import { combineProcessors } from '../../util/processors'; import { RELATED_REVIEW_PROMPT } from './prompts'; +import { deepCopySkipArrays } from './util'; export async function handleReview({ state, @@ -24,7 +25,7 @@ export async function handleReview({ current_processors: JSON.stringify(state.currentProcessors, null, 2), ex_answer: state.exAnswer, previous_error: state.previousError, - pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + pipeline_results: JSON.stringify(state.pipelineResults.map(deepCopySkipArrays), null, 2), })) as SimplifiedProcessor[]; const processors = { diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/util.test.ts b/x-pack/plugins/integration_assistant/server/graphs/related/util.test.ts new file mode 100644 index 0000000000000..c81369f98e56d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/util.test.ts @@ -0,0 +1,135 @@ +/* + * 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 { deepCopySkipArrays } from './util'; + +describe('deepCopySkipArrays', () => { + it('should skip arrays and deeply copy objects', () => { + const input = { + field: ['a', 'b'], + another: { field: 'c' }, + }; + + const expectedOutput = { + another: { field: 'c' }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); + + it('should return primitive types as is', () => { + expect(deepCopySkipArrays(42)).toBe(42); + expect(deepCopySkipArrays('string')).toBe('string'); + expect(deepCopySkipArrays(true)).toBe(true); + }); + + it('should handle nested objects and skip nested arrays', () => { + const input = { + level1: { + level2: { + array: [1, 2, 3], + value: 'test', + }, + }, + }; + + const expectedOutput = { + level1: { + level2: { + value: 'test', + }, + }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); + + it('should return undefined for arrays', () => { + expect(deepCopySkipArrays([1, 2, 3])).toBeUndefined(); + }); + + it('should handle null and undefined values', () => { + expect(deepCopySkipArrays(null)).toBeNull(); + expect(deepCopySkipArrays(undefined)).toBeUndefined(); + }); + + it('should handle empty objects', () => { + expect(deepCopySkipArrays({})).toEqual({}); + }); + + it('should handle objects with mixed types', () => { + const input = { + number: 1, + string: 'test', + boolean: true, + object: { key: 'value' }, + array: [1, 2, 3], + }; + + const expectedOutput = { + number: 1, + string: 'test', + boolean: true, + object: { key: 'value' }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); + + // Test case + it('should skip arrays and deeply copy objects with nested arrays', () => { + const input = { + field: ['a', 'b'], + another: { field: 'c' }, + }; + + const expectedOutput = { + another: { field: 'c' }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); + + it('should handle objects with nested empty arrays', () => { + const input = { + field: [], + another: { field: 'c' }, + }; + + const expectedOutput = { + another: { field: 'c' }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); + + it('should handle objects with nested arrays containing objects', () => { + const input = { + field: [{ key: 'value' }], + another: { field: 'c' }, + }; + + const expectedOutput = { + another: { field: 'c' }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); + + it('should handle objects with nested arrays containing mixed types', () => { + const input = { + field: [1, 'string', true, { key: 'value' }], + another: { field: 'c' }, + }; + + const expectedOutput = { + another: { field: 'c' }, + }; + + expect(deepCopySkipArrays(input)).toEqual(expectedOutput); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/util.ts b/x-pack/plugins/integration_assistant/server/graphs/related/util.ts new file mode 100644 index 0000000000000..b939e939fed32 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/util.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** + * Deeply copies a JSON object, skipping all arrays. + * + * @param value - The JSON value to be deeply copied, which can be an array, object, or other types. + * @returns A new object that is a deep copy of the input value, but with arrays skipped. + * + * This function recursively traverses the provided value. If the value is an array, it skips it. + * If the value is a regular object, it continues traversing its properties and copying them. + */ +export function deepCopySkipArrays(value: unknown): unknown { + if (Array.isArray(value)) { + // Skip arrays + return undefined; + } + + if (typeof value === 'object' && value !== null) { + // Regular dictionary, continue traversing. + const result: Record = {}; + for (const [k, v] of Object.entries(value)) { + const copiedValue = deepCopySkipArrays(v); + if (copiedValue !== undefined) { + result[k] = copiedValue; + } + } + return result; + } + + // For primitive types, return the value as is. + return value; +} From 204f9d3a2f2fef174e24f3a79eb6d7b2f2ef03f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Tue, 15 Oct 2024 15:37:19 +0100 Subject: [PATCH 35/84] [Stateful sidenav] Fix breadcrumbs (#196169) --- .../src/chrome_service.test.tsx | 31 ++++++++++++++++++- .../src/chrome_service.tsx | 17 ++++++++-- .../src/project_navigation/breadcrumbs.tsx | 9 +++--- .../project_navigation_service.ts | 7 ++--- .../core-chrome-browser-internal/src/types.ts | 7 +++-- .../src/chrome_service.mock.ts | 1 + .../core/chrome/core-chrome-browser/index.ts | 2 +- .../core-chrome-browser/src/breadcrumb.ts | 19 ++++++++++++ .../core-chrome-browser/src/contracts.ts | 8 +++-- .../chrome/core-chrome-browser/src/index.ts | 7 +++-- .../src/project_navigation.ts | 4 --- .../internal_dashboard_top_nav.tsx | 5 ++- src/plugins/management/public/plugin.tsx | 4 ++- .../public/navigation/breadcrumbs.ts | 6 +++- x-pack/plugins/serverless/public/types.ts | 4 +-- .../tests/observability_sidenav.ts | 2 +- .../tests/search_sidenav.ts | 2 +- 17 files changed, 104 insertions(+), 31 deletions(-) diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx index 7d7122e7387ce..4994302c2e756 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx @@ -392,7 +392,7 @@ describe('start', () => { describe('breadcrumbs', () => { it('updates/emits the current set of breadcrumbs', async () => { const { chrome, service } = await start(); - const promise = chrome.getBreadcrumbs$().pipe(toArray()).toPromise(); + const promise = firstValueFrom(chrome.getBreadcrumbs$().pipe(toArray())); chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); chrome.setBreadcrumbs([{ text: 'foo' }]); @@ -425,6 +425,35 @@ describe('start', () => { ] `); }); + + it('allows the project breadcrumb to also be set', async () => { + const { chrome } = await start(); + + chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); // only setting the classic breadcrumbs + + { + const breadcrumbs = await firstValueFrom(chrome.project.getBreadcrumbs$()); + expect(breadcrumbs.length).toBe(1); + expect(breadcrumbs[0]).toMatchObject({ + 'data-test-subj': 'deploymentCrumb', + }); + } + + chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }], { + project: { value: [{ text: 'baz' }] }, // also setting the project breadcrumb + }); + + { + const breadcrumbs = await firstValueFrom(chrome.project.getBreadcrumbs$()); + expect(breadcrumbs.length).toBe(2); + expect(breadcrumbs[0]).toMatchObject({ + 'data-test-subj': 'deploymentCrumb', + }); + expect(breadcrumbs[1]).toEqual({ + text: 'baz', // the project breadcrumb + }); + } + }); }); describe('breadcrumbsAppendExtension$', () => { diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 8ae1b7fb61cc5..5d86209ec8800 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -27,6 +27,7 @@ import type { ChromeNavLink, ChromeBadge, ChromeBreadcrumb, + ChromeSetBreadcrumbsParams, ChromeBreadcrumbsAppendExtension, ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, @@ -354,6 +355,17 @@ export class ChromeService { projectNavigation.setProjectBreadcrumbs(breadcrumbs, params); }; + const setClassicBreadcrumbs = ( + newBreadcrumbs: ChromeBreadcrumb[], + { project }: ChromeSetBreadcrumbsParams = {} + ) => { + breadcrumbs$.next(newBreadcrumbs); + if (project) { + const { value: projectValue, absolute = false } = project; + setProjectBreadcrumbs(projectValue ?? [], { absolute }); + } + }; + const setProjectHome = (homeHref: string) => { validateChromeStyle(); projectNavigation.setProjectHome(homeHref); @@ -507,9 +519,7 @@ export class ChromeService { getBreadcrumbs$: () => breadcrumbs$.pipe(takeUntil(this.stop$)), - setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => { - breadcrumbs$.next(newBreadcrumbs); - }, + setBreadcrumbs: setClassicBreadcrumbs, getBreadcrumbsAppendExtension$: () => breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$)), @@ -586,6 +596,7 @@ export class ChromeService { getNavigationTreeUi$: () => projectNavigation.getNavigationTreeUi$(), setSideNavComponent: setProjectSideNavComponent, setBreadcrumbs: setProjectBreadcrumbs, + getBreadcrumbs$: projectNavigation.getProjectBreadcrumbs$.bind(projectNavigation), getActiveNavigationNodes$: () => projectNavigation.getActiveNodes$(), updateSolutionNavigations: projectNavigation.updateSolutionNavigations, changeActiveSolutionNavigation: projectNavigation.changeActiveSolutionNavigation, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx index fe247f44fbadc..d6bc89deb2ce5 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx @@ -11,7 +11,6 @@ import React from 'react'; import { EuiContextMenuPanel, EuiContextMenuItem, EuiButtonEmpty } from '@elastic/eui'; import type { AppDeepLinkId, - ChromeProjectBreadcrumb, ChromeProjectNavigationNode, ChromeSetProjectBreadcrumbsParams, ChromeBreadcrumb, @@ -30,14 +29,14 @@ export function buildBreadcrumbs({ }: { projectName?: string; projectBreadcrumbs: { - breadcrumbs: ChromeProjectBreadcrumb[]; + breadcrumbs: ChromeBreadcrumb[]; params: ChromeSetProjectBreadcrumbsParams; }; chromeBreadcrumbs: ChromeBreadcrumb[]; cloudLinks: CloudLinks; activeNodes: ChromeProjectNavigationNode[][]; isServerless: boolean; -}): ChromeProjectBreadcrumb[] { +}): ChromeBreadcrumb[] { const rootCrumb = buildRootCrumb({ projectName, cloudLinks, @@ -54,7 +53,7 @@ export function buildBreadcrumbs({ (n) => Boolean(n.title) && n.breadcrumbStatus !== 'hidden' ); const navBreadcrumbs = navBreadcrumbPath.map( - (node): ChromeProjectBreadcrumb => ({ + (node): ChromeBreadcrumb => ({ href: node.deepLink?.url ?? node.href, deepLinkId: node.deepLink?.id as AppDeepLinkId, text: node.title, @@ -99,7 +98,7 @@ function buildRootCrumb({ projectName?: string; cloudLinks: CloudLinks; isServerless: boolean; -}): ChromeProjectBreadcrumb { +}): ChromeBreadcrumb { if (isServerless) { return { text: diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 6f77705069eaf..85c3fd1905adb 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -11,7 +11,6 @@ import { InternalApplicationStart } from '@kbn/core-application-browser-internal import type { ChromeNavLinks, SideNavComponent, - ChromeProjectBreadcrumb, ChromeBreadcrumb, ChromeSetProjectBreadcrumbsParams, ChromeProjectNavigationNode, @@ -80,7 +79,7 @@ export class ProjectNavigationService { ); private projectBreadcrumbs$ = new BehaviorSubject<{ - breadcrumbs: ChromeProjectBreadcrumb[]; + breadcrumbs: ChromeBreadcrumb[]; params: ChromeSetProjectBreadcrumbsParams; }>({ breadcrumbs: [], params: { absolute: false } }); private readonly stop$ = new ReplaySubject(1); @@ -153,7 +152,7 @@ export class ProjectNavigationService { return this.customProjectSideNavComponent$.asObservable(); }, setProjectBreadcrumbs: ( - breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[], + breadcrumbs: ChromeBreadcrumb | ChromeBreadcrumb[], params?: Partial ) => { this.projectBreadcrumbs$.next({ @@ -161,7 +160,7 @@ export class ProjectNavigationService { params: { absolute: false, ...params }, }); }, - getProjectBreadcrumbs$: (): Observable => { + getProjectBreadcrumbs$: (): Observable => { return combineLatest([ this.projectBreadcrumbs$, this.activeNodes$, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/types.ts b/packages/core/chrome/core-chrome-browser-internal/src/types.ts index a958eb59cd5f1..0e6bec4d2678c 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/types.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/types.ts @@ -9,8 +9,8 @@ import type { ChromeStart, + ChromeBreadcrumb, SideNavComponent, - ChromeProjectBreadcrumb, ChromeSetProjectBreadcrumbsParams, ChromeProjectNavigationNode, AppDeepLinkId, @@ -87,6 +87,9 @@ export interface InternalChromeStart extends ChromeStart { */ setSideNavComponent(component: SideNavComponent | null): void; + /** Get an Observable of the current project breadcrumbs */ + getBreadcrumbs$(): Observable; + /** * Set project breadcrumbs * @param breadcrumbs @@ -95,7 +98,7 @@ export interface InternalChromeStart extends ChromeStart { * Use {@link ServerlessPluginStart.setBreadcrumbs} to set project breadcrumbs. */ setBreadcrumbs( - breadcrumbs: ChromeProjectBreadcrumb[] | ChromeProjectBreadcrumb, + breadcrumbs: ChromeBreadcrumb[] | ChromeBreadcrumb, params?: Partial ): void; diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 144002ee94547..6be7bb68907eb 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -84,6 +84,7 @@ const createStartContractMock = () => { initNavigation: jest.fn(), setSideNavComponent: jest.fn(), setBreadcrumbs: jest.fn(), + getBreadcrumbs$: jest.fn(), getActiveNavigationNodes$: jest.fn(), getNavigationTreeUi$: jest.fn(), changeActiveSolutionNavigation: jest.fn(), diff --git a/packages/core/chrome/core-chrome-browser/index.ts b/packages/core/chrome/core-chrome-browser/index.ts index 4400c5e7d2b3f..afb2050d12e80 100644 --- a/packages/core/chrome/core-chrome-browser/index.ts +++ b/packages/core/chrome/core-chrome-browser/index.ts @@ -12,6 +12,7 @@ export type { AppId, ChromeBadge, ChromeBreadcrumb, + ChromeSetBreadcrumbsParams, ChromeBreadcrumbsAppendExtension, ChromeDocTitle, ChromeGlobalHelpExtensionMenuLink, @@ -41,7 +42,6 @@ export type { SideNavCompProps, SideNavComponent, SideNavNodeStatus, - ChromeProjectBreadcrumb, ChromeSetProjectBreadcrumbsParams, NodeDefinition, NodeDefinitionWithChildren, diff --git a/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts b/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts index 0a655b7706308..c0067030b7b0c 100644 --- a/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts +++ b/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts @@ -24,3 +24,22 @@ export interface ChromeBreadcrumb extends EuiBreadcrumb { export interface ChromeBreadcrumbsAppendExtension { content: MountPoint; } + +/** @public */ +export interface ChromeSetBreadcrumbsParams { + /** + * Declare the breadcrumbs for the project/solution type navigation in stateful. + * Those breadcrumbs correspond to the serverless breadcrumbs declaration. + */ + project?: { + /** + * The breadcrumb value to set. Can be a single breadcrumb or an array of breadcrumbs. + */ + value: ChromeBreadcrumb | ChromeBreadcrumb[]; + /** + * Indicates whether the breadcrumb should be absolute (replaces the full path) or relative. + * @default false + */ + absolute?: boolean; + }; +} diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts index aa2e4cf23ebbb..f5b5d1f0eaf12 100644 --- a/packages/core/chrome/core-chrome-browser/src/contracts.ts +++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts @@ -13,7 +13,11 @@ import type { ChromeRecentlyAccessed } from './recently_accessed'; import type { ChromeDocTitle } from './doc_title'; import type { ChromeHelpMenuLink, ChromeNavControls } from './nav_controls'; import type { ChromeHelpExtension } from './help_extension'; -import type { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from './breadcrumb'; +import type { + ChromeBreadcrumb, + ChromeBreadcrumbsAppendExtension, + ChromeSetBreadcrumbsParams, +} from './breadcrumb'; import type { ChromeBadge, ChromeStyle, ChromeUserBanner } from './types'; import type { ChromeGlobalHelpExtensionMenuLink } from './help_extension'; import type { PanelSelectedNode } from './project_navigation'; @@ -84,7 +88,7 @@ export interface ChromeStart { /** * Override the current set of breadcrumbs */ - setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[], params?: ChromeSetBreadcrumbsParams): void; /** * Get an observable of the current extension appended to breadcrumbs diff --git a/packages/core/chrome/core-chrome-browser/src/index.ts b/packages/core/chrome/core-chrome-browser/src/index.ts index 7247bfe69710a..efc2fb5636d84 100644 --- a/packages/core/chrome/core-chrome-browser/src/index.ts +++ b/packages/core/chrome/core-chrome-browser/src/index.ts @@ -7,7 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export type { ChromeBreadcrumbsAppendExtension, ChromeBreadcrumb } from './breadcrumb'; +export type { + ChromeBreadcrumbsAppendExtension, + ChromeBreadcrumb, + ChromeSetBreadcrumbsParams, +} from './breadcrumb'; export type { ChromeStart } from './contracts'; export type { ChromeDocTitle } from './doc_title'; export type { @@ -42,7 +46,6 @@ export type { SideNavComponent, SideNavNodeStatus, ChromeSetProjectBreadcrumbsParams, - ChromeProjectBreadcrumb, NodeDefinition, NodeDefinitionWithChildren, RenderAs as NodeRenderAs, diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts index 417deea8e003e..3e6afeb8f6117 100644 --- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts +++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts @@ -39,7 +39,6 @@ import type { AppId as SecurityApp, DeepLinkId as SecurityLink } from '@kbn/deep import type { AppId as FleetApp, DeepLinkId as FleetLink } from '@kbn/deeplinks-fleet'; import type { AppId as SharedApp, DeepLinkId as SharedLink } from '@kbn/deeplinks-shared'; -import type { ChromeBreadcrumb } from './breadcrumb'; import type { ChromeNavLink } from './nav_links'; import type { ChromeRecentlyAccessedHistoryItem } from './recently_accessed'; @@ -262,9 +261,6 @@ export interface SideNavCompProps { /** @public */ export type SideNavComponent = ComponentType; -/** @public */ -export type ChromeProjectBreadcrumb = ChromeBreadcrumb; - /** @public */ export interface ChromeSetProjectBreadcrumbsParams { absolute: boolean; diff --git a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx index bdbb506dfc713..6ca2298272c08 100644 --- a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx @@ -189,7 +189,10 @@ export function InternalDashboardTopNav({ }, }, ...dashboardTitleBreadcrumbs, - ]) + ]), + { + project: { value: dashboardTitleBreadcrumbs }, + } ); } }, [redirectTo, dashboardTitle, dashboardApi, viewMode, customLeadingBreadCrumbs]); diff --git a/src/plugins/management/public/plugin.tsx b/src/plugins/management/public/plugin.tsx index 8f8f0f6c0339b..97778792316ea 100644 --- a/src/plugins/management/public/plugin.tsx +++ b/src/plugins/management/public/plugin.tsx @@ -131,7 +131,9 @@ export class ManagementPlugin const [, ...trailingBreadcrumbs] = newBreadcrumbs; deps.serverless.setBreadcrumbs(trailingBreadcrumbs); } else { - coreStart.chrome.setBreadcrumbs(newBreadcrumbs); + coreStart.chrome.setBreadcrumbs(newBreadcrumbs, { + project: { value: newBreadcrumbs, absolute: true }, + }); } }, isSidebarEnabled$: managementPlugin.isSidebarEnabled$, diff --git a/x-pack/plugins/security_solution_ess/public/navigation/breadcrumbs.ts b/x-pack/plugins/security_solution_ess/public/navigation/breadcrumbs.ts index f0305cfb95511..476be6172d597 100644 --- a/x-pack/plugins/security_solution_ess/public/navigation/breadcrumbs.ts +++ b/x-pack/plugins/security_solution_ess/public/navigation/breadcrumbs.ts @@ -10,6 +10,10 @@ import type { Services } from '../common/services'; export const subscribeBreadcrumbs = (services: Services) => { const { securitySolution, chrome } = services; securitySolution.getBreadcrumbsNav$().subscribe((breadcrumbsNav) => { - chrome.setBreadcrumbs([...breadcrumbsNav.leading, ...breadcrumbsNav.trailing]); + chrome.setBreadcrumbs([...breadcrumbsNav.leading, ...breadcrumbsNav.trailing], { + project: { + value: breadcrumbsNav.trailing, + }, + }); }); }; diff --git a/x-pack/plugins/serverless/public/types.ts b/x-pack/plugins/serverless/public/types.ts index 7613cd50c0743..4627d24659b8e 100644 --- a/x-pack/plugins/serverless/public/types.ts +++ b/x-pack/plugins/serverless/public/types.ts @@ -6,7 +6,7 @@ */ import type { - ChromeProjectBreadcrumb, + ChromeBreadcrumb, ChromeSetProjectBreadcrumbsParams, SideNavComponent, NavigationTreeDefinition, @@ -21,7 +21,7 @@ export interface ServerlessPluginSetup {} export interface ServerlessPluginStart { setBreadcrumbs: ( - breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[], + breadcrumbs: ChromeBreadcrumb | ChromeBreadcrumb[], params?: Partial ) => void; setProjectHome(homeHref: string): void; diff --git a/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts b/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts index f2712fd6cf5e7..b28469a935fe4 100644 --- a/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts +++ b/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts @@ -82,7 +82,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await solutionNavigation.sidenav.openSection('project_settings_project_nav'); await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' }); await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' }); - await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' }); // navigate back to the home page using header logo await solutionNavigation.clickLogo(); diff --git a/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts b/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts index eb69631b09b0e..f90ea3e7b705f 100644 --- a/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts +++ b/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await solutionNavigation.sidenav.openSection('project_settings_project_nav'); await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' }); await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' }); - await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' }); // navigate back to the home page using header logo await solutionNavigation.clickLogo(); From 2c21adb8faafc0016ad7a6591837118f6bdf0907 Mon Sep 17 00:00:00 2001 From: Andrew Macri Date: Tue, 15 Oct 2024 10:39:48 -0400 Subject: [PATCH 36/84] [Security Solution] [Attack discovery] Output chunking / refinement, LangGraph migration, and evaluation improvements (#195669) ## [Security Solution] [Attack discovery] Output chunking / refinement, LangGraph migration, and evaluation improvements ### Summary This PR improves the Attack discovery user and developer experience with output chunking / refinement, migration to LangGraph, and improvements to evaluations. The improvements were realized by transitioning from directly using lower-level LangChain apis to LangGraph in this PR, and a deeper integration with the evaluation features of LangSmith. #### Output chunking _Output chunking_ increases the maximum and default number of alerts sent as context, working around the output token limitations of popular large language models (LLMs): | | Old | New | |----------------|-------|-------| | max alerts | `100` | `500` | | default alerts | `20` | `200` | See _Output chunking details_ below for more information. #### Settings A new settings modal makes it possible to configure the number of alerts sent as context directly from the Attack discovery page: ![settings](https://github.com/user-attachments/assets/3f5ab4e9-5eae-4f99-8490-e392c758fa6e) - Previously, users configured this value for Attack discovery via the security assistant Knowledge base settings, as documented [here](https://www.elastic.co/guide/en/security/8.15/attack-discovery.html#attack-discovery-generate-discoveries) - The new settings modal uses local storage (instead of the previously-shared assistant Knowledge base setting, which is stored in Elasticsearch) #### Output refinement _Output refinement_ automatically combines related discoveries (that were previously represented as two or more discoveries): ![default_attack_discovery_graph](https://github.com/user-attachments/assets/c092bb42-a41e-4fba-85c2-a4b2c1ef3053) - The `refine` step in the graph diagram above may (for example), combine three discoveries from the `generate` step into two discoveries when they are related ### Hallucination detection New _hallucination detection_ displays an error in lieu of showing hallucinated output: ![hallucination_detection](https://github.com/user-attachments/assets/1d849908-3f10-4fe8-8741-c0cf418b1524) - A new tour step was added to the Attack discovery page to share the improvements: ![tour_step](https://github.com/user-attachments/assets/0cedf770-baba-41b1-8ec6-b12b14c0c57a) ### Summary of improvements for developers The following features improve the developer experience when running evaluations for Attack discovery: #### Replay alerts in evaluations This evaluation feature eliminates the need to populate a local environment with alerts to (re)run evaluations: ![alerts_as_input](https://github.com/user-attachments/assets/b29dc847-3d53-4b17-8757-ed59852c1623) Alert replay skips the `retrieve_anonymized_alerts` step in the graph, because it uses the `anonymizedAlerts` and `replacements` provided as `Input` in a dataset example. See _Replay alerts in evaluations details_ below for more information. #### Override graph state Override graph state via datatset examples to test prompt improvements and edge cases via evaluations: ![override_graph_input](https://github.com/user-attachments/assets/a685177b-1e07-4f49-9b8d-c0b652975237) To use this feature, add an `overrides` key to the `Input` of a dataset example. See _Override graph state details_ below for more information. #### New custom evaluator Prior to this PR, an evaluator had to be manually added to each dataset in LangSmith to use an LLM as the judge for correctness. This PR introduces a custom, programmatic evaluator that handles anonymization automatically, and eliminates the need to manually create evaluators in LangSmith. To use it, simply run evaluations from the `Evaluation` tab in settings. #### New evaluation settings This PR introduces new settings in the `Evaluation` tab: ![new_evaluation_settings](https://github.com/user-attachments/assets/ca72aa2a-b0dc-4bec-9409-386d77d6a2f4) New evaluation settings: - `Evaluator model (optional)` - Judge the quality of predictions using a single model. (Default: use the same model as the connector) This new setting is useful when you want to use the same model, e.g. `GPT-4o` to judge the quality of all the models evaluated in an experiment. - `Default max alerts` - The default maximum number of alerts to send as context, which may be overridden by the example input This new setting is useful when using the alerts in the local environment to run evaluations. Examples that use the Alerts replay feature will ignore this value, because the alerts in the example `Input` will be used instead. #### Directory structure refactoring - The server-side directory structure was refactored to consolidate the location of Attack discovery related files ### Details This section describes some of the improvements above in detail. #### Output chunking details The new output chunking feature increases the maximum and default number of alerts that may be sent as context. It achieves this improvement by working around output token limitations. LLMs have different limits for the number of tokens accepted as _input_ for requests, and the number of tokens available for _output_ when generating responses. Today, the output token limits of most popular models are significantly smaller than the input token limits. For example, at the time of this writing, the Gemini 1.5 Pro model's limits are ([source](https://ai.google.dev/gemini-api/docs/models/gemini)): - Input token limit: `2,097,152` - Output token limit: `8,192` As a result of this relatively smaller output token limit, previous versions of Attack discovery would simply fail when an LLM ran out of output tokens when generating a response. This often happened "mid sentence", and resulted in errors or hallucinations being displayed to users. The new output chunking feature detects incomplete responses from the LLM in the `generate` step of the Graph. When an incomplete response is detected, the `generate` step will run again with: - The original prompt - The Alerts provided as context - The partially generated response - Instructions to "continue where you left off" The `generate` step in the graph will run until one of the following conditions is met: - The incomplete response can be successfully parsed - The maximum number of generation attempts (default: `10`) is reached - The maximum number of hallucinations detected (default: `5`) is reached #### Output refinement details The new output refinement feature automatically combines related discoveries (that were previously represented as two or more discoveries). The new `refine` step in the graph re-submits the discoveries from the `generate` step with a `refinePrompt` to combine related attack discoveries. The `refine` step is subject to the model's output token limits, just like the `generate` step. That means a response to the refine prompt from the LLM may be cut off "mid" sentence. To that end: - The refine step will re-run until the (same, shared) `maxGenerationAttempts` and `maxHallucinationFailures` limits as the `generate` step are reached - The maximum number of attempts (default: `10`) is _shared_ with the `generate` step. For example, if it took `7` tries (`generationAttempts`) to complete the `generate` step, the refine `step` will only run up to `3` times. The `refine` step will return _unrefined_ results from the `generate` step when: - The `generate` step uses all `10` generation attempts. When this happens, the `refine` step will be skipped, and the unrefined output of the `generate` step will be returned to the user - If the `refine` step uses all remaining attempts, but fails to produce a refined response, due to output token limitations, or hallucinations in the refined response #### Hallucination detection details Before this PR, Attack discovery directly used lower level LangChain APIs to parse responses from the LLM. After this PR, Attack discovery uses LangGraph. In the previous implementation, when Attack discovery received an incomplete response because the output token limits of a model were hit, the LangChain APIs automatically re-submitted the incomplete response in an attempt to "repair" it. However, the re-submitted results didn't include all of the original context (i.e. alerts that generated them). The repair process often resulted in hallucinated results being presented to users, especially with some models i.e. `Claude 3.5 Haiku`. In this PR, the `generate` and `refine` steps detect (some) hallucinations. When hallucinations are detected: - The current accumulated `generations` or `refinements` are (respectively) discarded, effectively restarting the `generate` or `refine` process - The `generate` and `refine` steps will be retried until the maximum generation attempts (default: `10`) or hallucinations detected (default: `5`) limits are reached Hitting the hallucination limit during the `generate` step will result in an error being displayed to the user. Hitting the hallucination limit during the `refine` step will result in the unrefined discoveries being displayed to the user. #### Replay alerts in evaluations details Alerts replay makes it possible to re-run evaluations, even when your local deployment has zero alerts. This feature eliminates the chore of populating your local instance with specific alerts for each example. Every example in a dataset may (optionally) specify a different set of alerts. Alert replay skips the `retrieve_anonymized_alerts` step in the graph, because it uses the `anonymizedAlerts` and `replacements` provided as `Input` in a dataset example. The following instructions document the process of creating a new LangSmith dataset example that uses the Alerts replay feature: 1) In Kibana, navigate to Security > Attack discovery 2) Click `Generate` to generate Attack discoveries 3) In LangSmith, navigate to Projects > _Your project_ 4) In the `Runs` tab of the LangSmith project, click on the latest `Attack discovery` entry to open the trace 5) **IMPORTANT**: In the trace, select the **LAST** `ChannelWriteChannelWrite `Add to Dataset` 7) Copy-paste the `Input` to the `Output`, because evaluation Experiments always compare the current run with the `Output` in an example. - This step is _always_ required to create a dataset. - If you don't want to use the Alert replay feature, replace `Input` with an empty object: ```json {} ``` 8) Choose an existing dataset, or create a new one 9) Click the `Submit` button to add the example to the dataset. After completing the steps above, the dataset is ready to be run in evaluations. #### Override graph state details When a dataset is run in an evaluation (to create Experiments): - The (optional) `anonymizedAlerts` and `replacements` provided as `Input` in the example will be replayed, bypassing the `retrieve_anonymized_alerts` step in the graph - The rest of the properties in `Input` will not be used as inputs to the graph - In contrast, an empty object `{}` in `Input` means the latest and riskiest alerts in the last 24 hours in the local environment will be queried In addition to the above, you may add an optional `overrides` key in the `Input` of a dataset example to test changes or edge cases. This is useful for evaluating changes without updating the code directly. The `overrides` set the initial state of the graph before it's run in an evaluation. The example `Input` below overrides the prompts used in the `generate` and `refine` steps: ```json { "overrides": { "refinePrompt": "This overrides the refine prompt", "attackDiscoveryPrompt": "This overrides the attack discovery prompt" } } ``` To use the `overrides` feature in evaluations to set the initial state of the graph: 1) Create a dataset example, as documented in the _Replay alerts in evaluations details_ section above 2) In LangSmith, navigate to Datasets & Testing > _Your Dataset_ 3) In the dataset, click the Examples tab 4) Click an example to open it in the flyout 5) Click the `Edit` button to edit the example 6) Add the `overrides` key shown below to the `Input` e.g.: ```json { "overrides": { "refinePrompt": "This overrides the refine prompt", "attackDiscoveryPrompt": "This overrides the attack discovery prompt" } } ``` 7) Edit the `overrides` in the example `Input` above to add (or remove) entries that will determine the initial state of the graph. All of the `overides` shown in step 6 are optional. The `refinePrompt` and `attackDiscoveryPrompt` could be removed from the `overrides` example above, and replaced with `maxGenerationAttempts` to test a higher limit. All valid graph state may be specified in `overrides`. --- .../index.test.ts} | 2 +- .../index.ts} | 7 +- .../get_raw_data_or_default/index.test.ts | 28 + .../helpers/get_raw_data_or_default/index.ts | 13 + .../helpers/is_raw_data_valid/index.test.ts | 51 + .../alerts/helpers/is_raw_data_valid/index.ts | 11 + .../size_is_out_of_range/index.test.ts | 47 + .../helpers/size_is_out_of_range/index.ts | 12 + .../impl/alerts/helpers/types.ts | 14 + .../attack_discovery/common_attributes.gen.ts | 4 +- .../common_attributes.schema.yaml | 2 - .../evaluation/post_evaluate_route.gen.ts | 2 + .../post_evaluate_route.schema.yaml | 4 + .../kbn-elastic-assistant-common/index.ts | 16 + .../alerts_settings/alerts_settings.tsx | 3 +- .../alerts_settings_management.tsx | 1 + .../evaluation_settings.tsx | 64 +- .../evaluation_settings/translations.ts | 30 + .../impl/assistant_context/constants.tsx | 5 + .../impl/assistant_context/index.tsx | 5 +- .../impl/knowledge_base/alerts_range.tsx | 64 +- .../packages/kbn-elastic-assistant/index.ts | 20 + x-pack/plugins/elastic_assistant/README.md | 10 +- .../docs/img/default_assistant_graph.png | Bin 30104 -> 29798 bytes .../img/default_attack_discovery_graph.png | Bin 0 -> 22551 bytes .../scripts/draw_graph_script.ts | 46 +- .../__mocks__/attack_discovery_schema.mock.ts | 2 +- .../server/__mocks__/data_clients.mock.ts | 2 +- .../server/__mocks__/request_context.ts | 2 +- .../server/__mocks__/response.ts | 2 +- .../server/ai_assistant_service/index.ts | 4 +- .../evaluation/__mocks__/mock_examples.ts | 55 + .../evaluation/__mocks__/mock_runs.ts | 53 + .../attack_discovery/evaluation/constants.ts | 911 +++++++++++ .../evaluation/example_input/index.test.ts | 75 + .../evaluation/example_input/index.ts | 52 + .../get_default_prompt_template/index.test.ts | 42 + .../get_default_prompt_template/index.ts | 33 + .../index.test.ts | 125 ++ .../index.ts | 29 + .../index.test.ts | 117 ++ .../index.ts | 27 + .../get_custom_evaluator/index.test.ts | 98 ++ .../helpers/get_custom_evaluator/index.ts | 69 + .../index.test.ts | 79 + .../index.ts | 39 + .../helpers/get_evaluator_llm/index.test.ts | 161 ++ .../helpers/get_evaluator_llm/index.ts | 65 + .../get_graph_input_overrides/index.test.ts | 121 ++ .../get_graph_input_overrides/index.ts | 29 + .../lib/attack_discovery/evaluation/index.ts | 122 ++ .../evaluation/run_evaluations/index.ts | 113 ++ .../constants.ts | 21 + .../index.test.ts | 22 + .../get_generate_or_end_decision/index.ts | 9 + .../edges/generate_or_end/index.test.ts | 72 + .../edges/generate_or_end/index.ts | 38 + .../index.test.ts | 43 + .../index.ts | 28 + .../helpers/get_should_end/index.test.ts | 60 + .../helpers/get_should_end/index.ts | 16 + .../generate_or_refine_or_end/index.test.ts | 118 ++ .../edges/generate_or_refine_or_end/index.ts | 66 + .../edges/helpers/get_has_results/index.ts | 11 + .../helpers/get_has_zero_alerts/index.ts | 12 + .../get_refine_or_end_decision/index.ts | 25 + .../helpers/get_should_end/index.ts | 16 + .../edges/refine_or_end/index.ts | 61 + .../get_retrieve_or_generate/index.ts | 13 + .../index.ts | 36 + .../index.ts | 14 + .../helpers/get_max_retries_reached/index.ts | 14 + .../default_attack_discovery_graph/index.ts | 122 ++ .../mock/mock_anonymization_fields.ts | 0 ...en_and_acknowledged_alerts_qery_results.ts | 25 + ...n_and_acknowledged_alerts_query_results.ts | 1396 +++++++++++++++++ .../discard_previous_generations/index.ts | 30 + .../get_alerts_context_prompt/index.test.ts} | 17 +- .../get_alerts_context_prompt/index.ts | 22 + .../get_anonymized_alerts_from_state/index.ts | 11 + .../get_use_unrefined_results/index.ts | 27 + .../nodes/generate/index.ts | 154 ++ .../nodes/generate/schema/index.ts | 84 + .../index.ts | 20 + .../nodes/helpers/extract_json/index.test.ts | 67 + .../nodes/helpers/extract_json/index.ts | 17 + .../generations_are_repeating/index.test.tsx | 90 ++ .../generations_are_repeating/index.tsx | 25 + .../index.ts | 34 + .../nodes/helpers/get_combined/index.ts | 14 + .../index.ts | 43 + .../helpers/get_continue_prompt/index.ts | 15 + .../index.ts | 9 + .../helpers/get_output_parser/index.test.ts | 31 + .../nodes/helpers/get_output_parser/index.ts | 13 + .../helpers/parse_combined_or_throw/index.ts | 53 + .../helpers/response_is_hallucinated/index.ts | 9 + .../discard_previous_refinements/index.ts | 30 + .../get_combined_refine_prompt/index.ts | 48 + .../get_default_refine_prompt/index.ts | 11 + .../get_use_unrefined_results/index.ts | 17 + .../nodes/refine/index.ts | 166 ++ .../anonymized_alerts_retriever/index.ts | 74 + .../get_anonymized_alerts/index.test.ts} | 18 +- .../helpers/get_anonymized_alerts/index.ts} | 14 +- .../nodes/retriever/index.ts | 70 + .../state/index.ts | 86 + .../default_attack_discovery_graph/types.ts | 28 + .../create_attack_discovery.test.ts | 4 +- .../create_attack_discovery.ts | 4 +- .../field_maps_configuration.ts | 0 .../find_all_attack_discoveries.ts | 4 +- ...d_attack_discovery_by_connector_id.test.ts | 2 +- .../find_attack_discovery_by_connector_id.ts | 4 +- .../get_attack_discovery.test.ts | 2 +- .../get_attack_discovery.ts | 4 +- .../attack_discovery/persistence}/index.ts | 15 +- .../persistence/transforms}/transforms.ts | 2 +- .../attack_discovery/persistence}/types.ts | 4 +- .../update_attack_discovery.test.ts | 4 +- .../update_attack_discovery.ts | 6 +- .../server/lib/langchain/graphs/index.ts | 35 +- .../{ => get}/get_attack_discovery.test.ts | 25 +- .../{ => get}/get_attack_discovery.ts | 8 +- .../routes/attack_discovery/helpers.test.ts | 805 ---------- .../attack_discovery/helpers/helpers.test.ts | 273 ++++ .../attack_discovery/{ => helpers}/helpers.ts | 231 +-- .../cancel}/cancel_attack_discovery.test.ts | 24 +- .../cancel}/cancel_attack_discovery.ts | 10 +- .../post/helpers/handle_graph_error/index.tsx | 73 + .../invoke_attack_discovery_graph/index.tsx | 127 ++ .../helpers/request_is_valid/index.test.tsx | 87 + .../post/helpers/request_is_valid/index.tsx | 33 + .../throw_if_error_counts_exceeded/index.ts | 44 + .../translations.ts | 28 + .../{ => post}/post_attack_discovery.test.ts | 40 +- .../{ => post}/post_attack_discovery.ts | 80 +- .../evaluate/get_graphs_from_names/index.ts | 35 + .../server/routes/evaluate/post_evaluate.ts | 43 +- .../server/routes/evaluate/utils.ts | 2 +- .../elastic_assistant/server/routes/index.ts | 4 +- .../server/routes/register_routes.ts | 6 +- .../plugins/elastic_assistant/server/types.ts | 4 +- .../actionable_summary/index.tsx | 43 +- .../attack_discovery_panel/index.tsx | 11 +- .../attack_discovery_panel/title/index.tsx | 27 +- .../get_attack_discovery_markdown.ts | 2 +- .../attack_discovery/hooks/use_poll_api.tsx | 6 +- .../empty_prompt/animated_counter/index.tsx | 2 +- .../pages/empty_prompt/index.test.tsx | 72 +- .../pages/empty_prompt/index.tsx | 29 +- .../helpers/show_empty_states/index.ts | 36 + .../pages/empty_states/index.test.tsx | 33 +- .../pages/empty_states/index.tsx | 44 +- .../attack_discovery/pages/failure/index.tsx | 48 +- .../pages/failure/translations.ts | 13 +- .../attack_discovery/pages/generate/index.tsx | 36 + .../pages/header/index.test.tsx | 13 + .../attack_discovery/pages/header/index.tsx | 16 +- .../settings_modal/alerts_settings/index.tsx | 77 + .../header/settings_modal/footer/index.tsx | 57 + .../pages/header/settings_modal/index.tsx | 160 ++ .../settings_modal/is_tour_enabled/index.ts | 18 + .../header/settings_modal/translations.ts | 81 + .../attack_discovery/pages/helpers.test.ts | 4 + .../public/attack_discovery/pages/helpers.ts | 31 +- .../public/attack_discovery/pages/index.tsx | 104 +- .../pages/loading_callout/index.test.tsx | 3 +- .../pages/loading_callout/index.tsx | 13 +- .../get_loading_callout_alerts_count/index.ts | 24 + .../loading_messages/index.test.tsx | 4 +- .../loading_messages/index.tsx | 16 +- .../pages/no_alerts/index.test.tsx | 2 +- .../pages/no_alerts/index.tsx | 17 +- .../attack_discovery/pages/results/index.tsx | 112 ++ .../use_attack_discovery/helpers.test.ts | 25 +- .../use_attack_discovery/helpers.ts | 11 +- .../use_attack_discovery/index.test.tsx | 33 +- .../use_attack_discovery/index.tsx | 17 +- .../attack_discovery_tool.test.ts | 340 ---- .../attack_discovery/attack_discovery_tool.ts | 115 -- .../get_attack_discovery_prompt.ts | 20 - .../get_output_parser.test.ts | 31 - .../attack_discovery/get_output_parser.ts | 80 - .../server/assistant/tools/index.ts | 2 - .../helpers.test.ts | 117 -- .../open_and_acknowledged_alerts/helpers.ts | 22 - .../open_and_acknowledged_alerts_tool.test.ts | 3 +- .../open_and_acknowledged_alerts_tool.ts | 10 +- .../plugins/security_solution/tsconfig.json | 1 - 190 files changed, 8378 insertions(+), 2148 deletions(-) rename x-pack/{plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts => packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts} (96%) rename x-pack/{plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts => packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts} (87%) create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts create mode 100644 x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts rename x-pack/plugins/{security_solution/server/assistant/tools => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph}/mock/mock_anonymization_fields.ts (100%) create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts rename x-pack/plugins/{security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts} (70%) create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts rename x-pack/plugins/{security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts} (90%) rename x-pack/plugins/{security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts} (77%) create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/create_attack_discovery}/create_attack_discovery.test.ts (94%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/create_attack_discovery}/create_attack_discovery.ts (95%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/field_maps_configuration}/field_maps_configuration.ts (100%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/find_all_attack_discoveries}/find_all_attack_discoveries.ts (92%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/find_attack_discovery_by_connector_id}/find_attack_discovery_by_connector_id.test.ts (95%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/find_attack_discovery_by_connector_id}/find_attack_discovery_by_connector_id.ts (93%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/get_attack_discovery}/get_attack_discovery.test.ts (95%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/get_attack_discovery}/get_attack_discovery.ts (93%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence}/index.ts (92%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/transforms}/transforms.ts (98%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence}/types.ts (93%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/update_attack_discovery}/update_attack_discovery.test.ts (97%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/update_attack_discovery}/update_attack_discovery.ts (95%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => get}/get_attack_discovery.test.ts (85%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => get}/get_attack_discovery.ts (92%) delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => helpers}/helpers.ts (55%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post/cancel}/cancel_attack_discovery.test.ts (80%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post/cancel}/cancel_attack_discovery.ts (91%) create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post}/post_attack_discovery.test.ts (79%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post}/post_attack_discovery.ts (79%) create mode 100644 x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts similarity index 96% rename from x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts rename to x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts index c8b52779d7b42..975896f381443 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query'; +import { getOpenAndAcknowledgedAlertsQuery } from '.'; describe('getOpenAndAcknowledgedAlertsQuery', () => { it('returns the expected query', () => { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts similarity index 87% rename from x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts rename to x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts index 4090e71baa371..6f6e196053ca6 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts @@ -5,8 +5,13 @@ * 2.0. */ -import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { AnonymizationFieldResponse } from '../../schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +/** + * This query returns open and acknowledged (non-building block) alerts in the last 24 hours. + * + * The alerts are ordered by risk score, and then from the most recent to the oldest. + */ export const getOpenAndAcknowledgedAlertsQuery = ({ alertsIndexPattern, anonymizationFields, diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts new file mode 100644 index 0000000000000..899b156d21767 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { getRawDataOrDefault } from '.'; + +describe('getRawDataOrDefault', () => { + it('returns the raw data when it is valid', () => { + const rawData = { + field1: [1, 2, 3], + field2: ['a', 'b', 'c'], + }; + + expect(getRawDataOrDefault(rawData)).toEqual(rawData); + }); + + it('returns an empty object when the raw data is invalid', () => { + const rawData = { + field1: [1, 2, 3], + field2: 'invalid', + }; + + expect(getRawDataOrDefault(rawData)).toEqual({}); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts new file mode 100644 index 0000000000000..edbe320c95305 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRawDataValid } from '../is_raw_data_valid'; +import type { MaybeRawData } from '../types'; + +/** Returns the raw data if it valid, or a default if it's not */ +export const getRawDataOrDefault = (rawData: MaybeRawData): Record => + isRawDataValid(rawData) ? rawData : {}; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts new file mode 100644 index 0000000000000..cc205250e84db --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { isRawDataValid } from '.'; + +describe('isRawDataValid', () => { + it('returns true for valid raw data', () => { + const rawData = { + field1: [1, 2, 3], // the Fields API may return a number array + field2: ['a', 'b', 'c'], // the Fields API may return a string array + }; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns true when a field array is empty', () => { + const rawData = { + field1: [1, 2, 3], // the Fields API may return a number array + field2: ['a', 'b', 'c'], // the Fields API may return a string array + field3: [], // the Fields API may return an empty array + }; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns false when a field does not have an array of values', () => { + const rawData = { + field1: [1, 2, 3], + field2: 'invalid', + }; + + expect(isRawDataValid(rawData)).toBe(false); + }); + + it('returns true for empty raw data', () => { + const rawData = {}; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns false when raw data is an unexpected type', () => { + const rawData = 1234; + + // @ts-expect-error + expect(isRawDataValid(rawData)).toBe(false); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts new file mode 100644 index 0000000000000..1a9623b15ea98 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MaybeRawData } from '../types'; + +export const isRawDataValid = (rawData: MaybeRawData): rawData is Record => + typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x])); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts new file mode 100644 index 0000000000000..b118a5c94b26e --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sizeIsOutOfRange } from '.'; +import { MAX_SIZE, MIN_SIZE } from '../types'; + +describe('sizeIsOutOfRange', () => { + it('returns true when size is undefined', () => { + const size = undefined; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns true when size is less than MIN_SIZE', () => { + const size = MIN_SIZE - 1; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns true when size is greater than MAX_SIZE', () => { + const size = MAX_SIZE + 1; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns false when size is exactly MIN_SIZE', () => { + const size = MIN_SIZE; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + + it('returns false when size is exactly MAX_SIZE', () => { + const size = MAX_SIZE; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + + it('returns false when size is within the valid range', () => { + const size = MIN_SIZE + 1; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts new file mode 100644 index 0000000000000..b2a93b79cbb42 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MAX_SIZE, MIN_SIZE } from '../types'; + +/** Return true if the provided size is out of range */ +export const sizeIsOutOfRange = (size?: number): boolean => + size == null || size < MIN_SIZE || size > MAX_SIZE; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts new file mode 100644 index 0000000000000..5c81c99ce5732 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +export const MIN_SIZE = 10; +export const MAX_SIZE = 10000; + +/** currently the same shape as "fields" property in the ES response */ +export type MaybeRawData = SearchResponse['fields'] | undefined; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts index 9599e8596e553..8ade6084fd7de 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts @@ -39,7 +39,7 @@ export const AttackDiscovery = z.object({ /** * A short (no more than a sentence) summary of the attack discovery featuring only the host.name and user.name fields (when they are applicable), using the same syntax */ - entitySummaryMarkdown: z.string(), + entitySummaryMarkdown: z.string().optional(), /** * An array of MITRE ATT&CK tactic for the attack discovery */ @@ -55,7 +55,7 @@ export const AttackDiscovery = z.object({ /** * The time the attack discovery was generated */ - timestamp: NonEmptyString, + timestamp: NonEmptyString.optional(), }); /** diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml index dcb72147f9408..3adf2f7836804 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml @@ -12,9 +12,7 @@ components: required: - 'alertIds' - 'detailsMarkdown' - - 'entitySummaryMarkdown' - 'summaryMarkdown' - - 'timestamp' - 'title' properties: alertIds: diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts index b6d51b9bea3fc..a0cbc22282c7b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts @@ -22,10 +22,12 @@ export type PostEvaluateBody = z.infer; export const PostEvaluateBody = z.object({ graphs: z.array(z.string()), datasetName: z.string(), + evaluatorConnectorId: z.string().optional(), connectorIds: z.array(z.string()), runName: z.string().optional(), alertsIndexPattern: z.string().optional().default('.alerts-security.alerts-default'), langSmithApiKey: z.string().optional(), + langSmithProject: z.string().optional(), replacements: Replacements.optional().default({}), size: z.number().optional().default(20), }); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml index d0bec37344165..071d80156890b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml @@ -61,6 +61,8 @@ components: type: string datasetName: type: string + evaluatorConnectorId: + type: string connectorIds: type: array items: @@ -72,6 +74,8 @@ components: default: ".alerts-security.alerts-default" langSmithApiKey: type: string + langSmithProject: + type: string replacements: $ref: "../conversations/common_attributes.schema.yaml#/components/schemas/Replacements" default: {} diff --git a/x-pack/packages/kbn-elastic-assistant-common/index.ts b/x-pack/packages/kbn-elastic-assistant-common/index.ts index d8b4858d3ba8b..41ed86dacd9db 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/index.ts @@ -25,3 +25,19 @@ export { export { transformRawData } from './impl/data_anonymization/transform_raw_data'; export { parseBedrockBuffer, handleBedrockChunk } from './impl/utils/bedrock'; export * from './constants'; + +/** currently the same shape as "fields" property in the ES response */ +export { type MaybeRawData } from './impl/alerts/helpers/types'; + +/** + * This query returns open and acknowledged (non-building block) alerts in the last 24 hours. + * + * The alerts are ordered by risk score, and then from the most recent to the oldest. + */ +export { getOpenAndAcknowledgedAlertsQuery } from './impl/alerts/get_open_and_acknowledged_alerts_query'; + +/** Returns the raw data if it valid, or a default if it's not */ +export { getRawDataOrDefault } from './impl/alerts/helpers/get_raw_data_or_default'; + +/** Return true if the provided size is out of range */ +export { sizeIsOutOfRange } from './impl/alerts/helpers/size_is_out_of_range'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx index 60078178a1771..3b48c8d0861c5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx @@ -16,7 +16,7 @@ import * as i18n from '../../../knowledge_base/translations'; export const MIN_LATEST_ALERTS = 10; export const MAX_LATEST_ALERTS = 100; export const TICK_INTERVAL = 10; -export const RANGE_CONTAINER_WIDTH = 300; // px +export const RANGE_CONTAINER_WIDTH = 600; // px const LABEL_WRAPPER_MIN_WIDTH = 95; // px interface Props { @@ -52,6 +52,7 @@ const AlertsSettingsComponent = ({ knowledgeBase, setUpdatedKnowledgeBaseSetting diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx index 1a6f826bd415f..7a3998879078d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx @@ -40,6 +40,7 @@ export const AlertsSettingsManagement: React.FC = React.memo( knowledgeBase={knowledgeBase} setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} compressed={false} + value={knowledgeBase.latestAlerts} /> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx index cefc008eba992..ffbcad48d1cac 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx @@ -17,28 +17,34 @@ import { EuiComboBox, EuiButton, EuiComboBoxOptionOption, + EuiComboBoxSingleSelectionShape, EuiTextColor, EuiFieldText, + EuiFieldNumber, EuiFlexItem, EuiFlexGroup, EuiLink, EuiPanel, } from '@elastic/eui'; - import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { GetEvaluateResponse, PostEvaluateRequestBodyInput, } from '@kbn/elastic-assistant-common'; +import { isEmpty } from 'lodash/fp'; + import * as i18n from './translations'; import { useAssistantContext } from '../../../assistant_context'; +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '../../../assistant_context/constants'; import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; import { getActionTypeTitle, getGenAiConfig } from '../../../connectorland/helpers'; import { PRECONFIGURED_CONNECTOR } from '../../../connectorland/translations'; import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; import { useEvaluationData } from '../../api/evaluate/use_evaluation_data'; +const AS_PLAIN_TEXT: EuiComboBoxSingleSelectionShape = { asPlainText: true }; + /** * Evaluation Settings -- development-only feature for evaluating models */ @@ -121,6 +127,18 @@ export const EvaluationSettings: React.FC = React.memo(() => { }, [setSelectedModelOptions] ); + + const [selectedEvaluatorModel, setSelectedEvaluatorModel] = useState< + Array> + >([]); + + const onSelectedEvaluatorModelChange = useCallback( + (selected: Array>) => setSelectedEvaluatorModel(selected), + [] + ); + + const [size, setSize] = useState(`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`); + const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); const modelOptions = useMemo(() => { return ( @@ -170,19 +188,40 @@ export const EvaluationSettings: React.FC = React.memo(() => { // Perform Evaluation Button const handlePerformEvaluation = useCallback(async () => { + const evaluatorConnectorId = + selectedEvaluatorModel[0]?.key != null + ? { evaluatorConnectorId: selectedEvaluatorModel[0].key } + : {}; + + const langSmithApiKey = isEmpty(traceOptions.langSmithApiKey) + ? undefined + : traceOptions.langSmithApiKey; + + const langSmithProject = isEmpty(traceOptions.langSmithProject) + ? undefined + : traceOptions.langSmithProject; + const evalParams: PostEvaluateRequestBodyInput = { connectorIds: selectedModelOptions.flatMap((option) => option.key ?? []).sort(), graphs: selectedGraphOptions.map((option) => option.label).sort(), datasetName: selectedDatasetOptions[0]?.label, + ...evaluatorConnectorId, + langSmithApiKey, + langSmithProject, runName, + size: Number(size), }; performEvaluation(evalParams); }, [ performEvaluation, runName, selectedDatasetOptions, + selectedEvaluatorModel, selectedGraphOptions, selectedModelOptions, + size, + traceOptions.langSmithApiKey, + traceOptions.langSmithProject, ]); const getSection = (title: string, description: string) => ( @@ -355,6 +394,29 @@ export const EvaluationSettings: React.FC = React.memo(() => { onChange={onGraphOptionsChange} /> + + + + + + + setSize(e.target.value)} value={size} /> + diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts index 62902d0f14095..26eddb8a223c7 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts @@ -78,6 +78,36 @@ export const CONNECTORS_LABEL = i18n.translate( } ); +export const EVALUATOR_MODEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelLabel', + { + defaultMessage: 'Evaluator model (optional)', + } +); + +export const DEFAULT_MAX_ALERTS = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.defaultMaxAlertsLabel', + { + defaultMessage: 'Default max alerts', + } +); + +export const EVALUATOR_MODEL_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelDescription', + { + defaultMessage: + 'Judge the quality of all predictions using a single model. (Default: use the same model as the connector)', + } +); + +export const DEFAULT_MAX_ALERTS_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.defaultMaxAlertsDescription', + { + defaultMessage: + 'The default maximum number of alerts to send as context, which may be overridden by the Example input', + } +); + export const CONNECTORS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx index be7724d882278..92a2a3df2683b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -10,7 +10,9 @@ import { KnowledgeBaseConfig } from '../assistant/types'; export const ATTACK_DISCOVERY_STORAGE_KEY = 'attackDiscovery'; export const DEFAULT_ASSISTANT_NAMESPACE = 'elasticAssistantDefault'; export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId'; +export const MAX_ALERTS_LOCAL_STORAGE_KEY = 'maxAlerts'; export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase'; +export const SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY = 'showSettingsTour'; export const STREAMING_LOCAL_STORAGE_KEY = 'streaming'; export const TRACE_OPTIONS_SESSION_STORAGE_KEY = 'traceOptions'; export const CONVERSATION_TABLE_SESSION_STORAGE_KEY = 'conversationTable'; @@ -21,6 +23,9 @@ export const ANONYMIZATION_TABLE_SESSION_STORAGE_KEY = 'anonymizationTable'; /** The default `n` latest alerts, ordered by risk score, sent as context to the assistant */ export const DEFAULT_LATEST_ALERTS = 20; +/** The default maximum number of alerts to be sent as context when generating Attack discoveries */ +export const DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS = 200; + export const DEFAULT_KNOWLEDGE_BASE_SETTINGS: KnowledgeBaseConfig = { latestAlerts: DEFAULT_LATEST_ALERTS, }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index c7b15f681a717..2319bf67de89a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -262,7 +262,10 @@ export const AssistantProvider: React.FC = ({ docLinks, getComments, http, - knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, ...localStorageKnowledgeBase }, + knowledgeBase: { + ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, + ...localStorageKnowledgeBase, + }, promptContexts, navigateToApp, nameSpace, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx index 63bd86121dcc1..6cfa60eff282d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx @@ -7,7 +7,7 @@ import { EuiRange, useGeneratedHtmlId } from '@elastic/eui'; import { css } from '@emotion/react'; -import React from 'react'; +import React, { useCallback } from 'react'; import { MAX_LATEST_ALERTS, MIN_LATEST_ALERTS, @@ -16,35 +16,57 @@ import { import { KnowledgeBaseConfig } from '../assistant/types'; import { ALERTS_RANGE } from './translations'; +export type SingleRangeChangeEvent = + | React.ChangeEvent + | React.KeyboardEvent + | React.MouseEvent; + interface Props { - knowledgeBase: KnowledgeBaseConfig; - setUpdatedKnowledgeBaseSettings: React.Dispatch>; compressed?: boolean; + maxAlerts?: number; + minAlerts?: number; + onChange?: (e: SingleRangeChangeEvent) => void; + knowledgeBase?: KnowledgeBaseConfig; + setUpdatedKnowledgeBaseSettings?: React.Dispatch>; + step?: number; + value: string | number; } const MAX_ALERTS_RANGE_WIDTH = 649; // px export const AlertsRange: React.FC = React.memo( - ({ knowledgeBase, setUpdatedKnowledgeBaseSettings, compressed = true }) => { + ({ + compressed = true, + knowledgeBase, + maxAlerts = MAX_LATEST_ALERTS, + minAlerts = MIN_LATEST_ALERTS, + onChange, + setUpdatedKnowledgeBaseSettings, + step = TICK_INTERVAL, + value, + }) => { const inputRangeSliderId = useGeneratedHtmlId({ prefix: 'inputRangeSlider' }); - return ( - + const handleOnChange = useCallback( + (e: SingleRangeChangeEvent) => { + if (knowledgeBase != null && setUpdatedKnowledgeBaseSettings != null) { setUpdatedKnowledgeBaseSettings({ ...knowledgeBase, latestAlerts: Number(e.currentTarget.value), - }) + }); } - showTicks - step={TICK_INTERVAL} - value={knowledgeBase.latestAlerts} + + if (onChange != null) { + onChange(e); + } + }, + [knowledgeBase, onChange, setUpdatedKnowledgeBaseSettings] + ); + + return ( + = React.memo( margin-inline-end: 0; } `} + data-test-subj="alertsRange" + id={inputRangeSliderId} + max={maxAlerts} + min={minAlerts} + onChange={handleOnChange} + showTicks + step={step} + value={value} /> ); } diff --git a/x-pack/packages/kbn-elastic-assistant/index.ts b/x-pack/packages/kbn-elastic-assistant/index.ts index 0baff57648cc8..7ec65c9601268 100644 --- a/x-pack/packages/kbn-elastic-assistant/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/index.ts @@ -77,10 +77,17 @@ export { AssistantAvatar } from './impl/assistant/assistant_avatar/assistant_ava export { ConnectorSelectorInline } from './impl/connectorland/connector_selector_inline/connector_selector_inline'; export { + /** The Attack discovery local storage key */ ATTACK_DISCOVERY_STORAGE_KEY, DEFAULT_ASSISTANT_NAMESPACE, + /** The default maximum number of alerts to be sent as context when generating Attack discoveries */ + DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, DEFAULT_LATEST_ALERTS, KNOWLEDGE_BASE_LOCAL_STORAGE_KEY, + /** The local storage key that specifies the maximum number of alerts to send as context */ + MAX_ALERTS_LOCAL_STORAGE_KEY, + /** The local storage key that specifies whether the settings tour should be shown */ + SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY, } from './impl/assistant_context/constants'; export { useLoadConnectors } from './impl/connectorland/use_load_connectors'; @@ -140,3 +147,16 @@ export { mergeBaseWithPersistedConversations } from './impl/assistant/helpers'; export { UpgradeButtons } from './impl/upgrade/upgrade_buttons'; export { getUserConversations, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; + +export { + /** A range slider component, typically used to configure the number of alerts sent as context */ + AlertsRange, + /** This event occurs when the `AlertsRange` slider is changed */ + type SingleRangeChangeEvent, +} from './impl/knowledge_base/alerts_range'; +export { + /** A label instructing the user to send fewer alerts */ + SELECT_FEWER_ALERTS, + /** Your anonymization settings will apply to these alerts (label) */ + YOUR_ANONYMIZATION_SETTINGS, +} from './impl/knowledge_base/translations'; diff --git a/x-pack/plugins/elastic_assistant/README.md b/x-pack/plugins/elastic_assistant/README.md index 2a1e47c177591..8cf2c0b8903dd 100755 --- a/x-pack/plugins/elastic_assistant/README.md +++ b/x-pack/plugins/elastic_assistant/README.md @@ -10,15 +10,21 @@ Maintained by the Security Solution team ## Graph structure +### Default Assistant graph + ![DefaultAssistantGraph](./docs/img/default_assistant_graph.png) +### Default Attack discovery graph + +![DefaultAttackDiscoveryGraph](./docs/img/default_attack_discovery_graph.png) + ## Development ### Generate graph structure To generate the graph structure, run `yarn draw-graph` from the plugin directory. -The graph will be generated in the `docs/img` directory of the plugin. +The graphs will be generated in the `docs/img` directory of the plugin. ### Testing -To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory. \ No newline at end of file +To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory. diff --git a/x-pack/plugins/elastic_assistant/docs/img/default_assistant_graph.png b/x-pack/plugins/elastic_assistant/docs/img/default_assistant_graph.png index e4ef8382317e5f827778d1bb34984644f87bbf9d..159b69c6d95723f08843347b2bb14d75ec2f3905 100644 GIT binary patch literal 29798 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 literal 30104 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 S?F<&7j~_pA#Dr2mr~U_C`zs#+ diff --git a/x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png b/x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..658490900cca60fc511e729fc08e8ea5e411d60f GIT binary patch literal 22551 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts b/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts index c44912ebf8d94..3b65d307ce385 100644 --- a/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts +++ b/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts @@ -5,11 +5,13 @@ * 2.0. */ +import type { ElasticsearchClient } from '@kbn/core/server'; import { ToolingLog } from '@kbn/tooling-log'; import fs from 'fs/promises'; import path from 'path'; import { ActionsClientChatOpenAI, + type ActionsClientLlm, ActionsClientSimpleChatModel, } from '@kbn/langchain/server/language_models'; import type { Logger } from '@kbn/logging'; @@ -17,6 +19,11 @@ import { ChatPromptTemplate } from '@langchain/core/prompts'; import { FakeLLM } from '@langchain/core/utils/testing'; import { createOpenAIFunctionsAgent } from 'langchain/agents'; import { getDefaultAssistantGraph } from '../server/lib/langchain/graphs/default_assistant_graph/graph'; +import { getDefaultAttackDiscoveryGraph } from '../server/lib/attack_discovery/graphs/default_attack_discovery_graph'; + +interface Drawable { + drawMermaidPng: () => Promise; +} // Just defining some test variables to get the graph to compile.. const testPrompt = ChatPromptTemplate.fromMessages([ @@ -34,7 +41,7 @@ const createLlmInstance = () => { return mockLlm; }; -async function getGraph(logger: Logger) { +async function getAssistantGraph(logger: Logger): Promise { const agentRunnable = await createOpenAIFunctionsAgent({ llm: mockLlm, tools: [], @@ -51,16 +58,49 @@ async function getGraph(logger: Logger) { return graph.getGraph(); } -export const draw = async () => { +async function getAttackDiscoveryGraph(logger: Logger): Promise { + const mockEsClient = {} as unknown as ElasticsearchClient; + + const graph = getDefaultAttackDiscoveryGraph({ + anonymizationFields: [], + esClient: mockEsClient, + llm: mockLlm as unknown as ActionsClientLlm, + logger, + replacements: {}, + size: 20, + }); + + return graph.getGraph(); +} + +export const drawGraph = async ({ + getGraph, + outputFilename, +}: { + getGraph: (logger: Logger) => Promise; + outputFilename: string; +}) => { const logger = new ToolingLog({ level: 'info', writeTo: process.stdout, }) as unknown as Logger; logger.info('Compiling graph'); - const outputPath = path.join(__dirname, '../docs/img/default_assistant_graph.png'); + const outputPath = path.join(__dirname, outputFilename); const graph = await getGraph(logger); const output = await graph.drawMermaidPng(); const buffer = Buffer.from(await output.arrayBuffer()); logger.info(`Writing graph to ${outputPath}`); await fs.writeFile(outputPath, buffer); }; + +export const draw = async () => { + await drawGraph({ + getGraph: getAssistantGraph, + outputFilename: '../docs/img/default_assistant_graph.png', + }); + + await drawGraph({ + getGraph: getAttackDiscoveryGraph, + outputFilename: '../docs/img/default_attack_discovery_graph.png', + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts index 9e8a0b5d2ac90..ee54e9c451ea2 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts @@ -6,7 +6,7 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { EsAttackDiscoverySchema } from '../ai_assistant_data_clients/attack_discovery/types'; +import { EsAttackDiscoverySchema } from '../lib/attack_discovery/persistence/types'; export const getAttackDiscoverySearchEsMock = () => { const searchResponse: estypes.SearchResponse = { diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts index 7e20e292a9868..473965a835f14 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts @@ -8,7 +8,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; import { AIAssistantDataClient } from '../ai_assistant_data_clients'; -import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; type ConversationsDataClientContract = PublicMethodsOf; export type ConversationsDataClientMock = jest.Mocked; diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts index b52e7db536a3d..d53ceaa586975 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts @@ -26,7 +26,7 @@ import { GetAIAssistantKnowledgeBaseDataClientParams, } from '../ai_assistant_data_clients/knowledge_base'; import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; -import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts index def0a81acea37..ae736c77c30ef 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts @@ -16,7 +16,7 @@ import { getPromptsSearchEsMock } from './prompts_schema.mock'; import { EsAnonymizationFieldsSchema } from '../ai_assistant_data_clients/anonymization_fields/types'; import { getAnonymizationFieldsSearchEsMock } from './anonymization_fields_schema.mock'; import { getAttackDiscoverySearchEsMock } from './attack_discovery_schema.mock'; -import { EsAttackDiscoverySchema } from '../ai_assistant_data_clients/attack_discovery/types'; +import { EsAttackDiscoverySchema } from '../lib/attack_discovery/persistence/types'; export const responseMock = { create: httpServerMock.createResponseFactory, diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts index 08912f41a8bbc..4cde64424ed7e 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts @@ -11,7 +11,7 @@ import type { AuthenticatedUser, Logger, ElasticsearchClient } from '@kbn/core/s import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import { Subject } from 'rxjs'; -import { attackDiscoveryFieldMap } from '../ai_assistant_data_clients/attack_discovery/field_maps_configuration'; +import { attackDiscoveryFieldMap } from '../lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration'; import { getDefaultAnonymizationFields } from '../../common/anonymization'; import { AssistantResourceNames, GetElser } from '../types'; import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; @@ -34,7 +34,7 @@ import { AIAssistantKnowledgeBaseDataClient, GetAIAssistantKnowledgeBaseDataClientParams, } from '../ai_assistant_data_clients/knowledge_base'; -import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; import { createGetElserId, createPipeline, pipelineExists } from './helpers'; const TOTAL_FIELDS_LIMIT = 2500; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts new file mode 100644 index 0000000000000..d149b8c4cd44d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.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 type { Example } from 'langsmith/schemas'; + +export const exampleWithReplacements: Example = { + id: '5D436078-B2CF-487A-A0FA-7CB46696F54E', + created_at: '2024-10-10T23:01:19.350232+00:00', + dataset_id: '0DA3497B-B084-4105-AFC0-2D8E05DE4B7C', + modified_at: '2024-10-10T23:01:19.350232+00:00', + inputs: {}, + outputs: { + attackDiscoveries: [ + { + title: 'Critical Malware and Phishing Alerts on host e1cb3cf0-30f3-4f99-a9c8-518b955c6f90', + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + timestamp: '2024-10-10T22:59:52.749Z', + detailsMarkdown: + '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', + summaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} involving user {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', + mitreAttackTactics: ['Credential Access', 'Input Capture'], + entitySummaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} involving user {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}.', + }, + ], + replacements: { + '039c15c5-3964-43e7-a891-42fe2ceeb9ff': 'james', + '0b53f092-96dd-4282-bfb9-4f75a4530b80': 'root', + '1123bd7b-3afb-45d1-801a-108f04e7cfb7': 'SRVWIN04', + '3b9856bc-2c0d-4f1a-b9ae-32742e15ddd1': 'SRVWIN07', + '5306bcfd-2729-49e3-bdf0-678002778ccf': 'SRVWIN01', + '55af96a7-69b0-47cf-bf11-29be98a59eb0': 'SRVNIX05', + '66919fe3-16a4-4dfe-bc90-713f0b33a2ff': 'Administrator', + '9404361f-53fa-484f-adf8-24508256e70e': 'SRVWIN03', + 'e1cb3cf0-30f3-4f99-a9c8-518b955c6f90': 'SRVMAC08', + 'f59a00e2-f9c4-4069-8390-fd36ecd16918': 'SRVWIN02', + 'fc6d07da-5186-4d59-9b79-9382b0c226b3': 'SRVWIN06', + }, + }, + runs: [], +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts new file mode 100644 index 0000000000000..23c9c08ff5080 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Run } from 'langsmith/schemas'; + +export const runWithReplacements: Run = { + id: 'B7B03FEE-9AC4-4823-AEDB-F8EC20EAD5C4', + inputs: {}, + name: 'test', + outputs: { + attackDiscoveries: [ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` by the user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: + 'The host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` and user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}` were involved in the attack.', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` involving the user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ], + replacements: { + '039c15c5-3964-43e7-a891-42fe2ceeb9ff': 'james', + '0b53f092-96dd-4282-bfb9-4f75a4530b80': 'root', + '1123bd7b-3afb-45d1-801a-108f04e7cfb7': 'SRVWIN04', + '3b9856bc-2c0d-4f1a-b9ae-32742e15ddd1': 'SRVWIN07', + '5306bcfd-2729-49e3-bdf0-678002778ccf': 'SRVWIN01', + '55af96a7-69b0-47cf-bf11-29be98a59eb0': 'SRVNIX05', + '66919fe3-16a4-4dfe-bc90-713f0b33a2ff': 'Administrator', + '9404361f-53fa-484f-adf8-24508256e70e': 'SRVWIN03', + 'e1cb3cf0-30f3-4f99-a9c8-518b955c6f90': 'SRVMAC08', + 'f59a00e2-f9c4-4069-8390-fd36ecd16918': 'SRVWIN02', + 'fc6d07da-5186-4d59-9b79-9382b0c226b3': 'SRVWIN06', + }, + }, + run_type: 'evaluation', +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts new file mode 100644 index 0000000000000..c6f6f09f1d9ae --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts @@ -0,0 +1,911 @@ +/* + * 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 { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; + +export const DEFAULT_EVAL_ANONYMIZATION_FIELDS: AnonymizationFieldResponse[] = [ + { + id: 'Mx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: '_id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'NB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: '@timestamp', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'NR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'cloud.availability_zone', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Nh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'cloud.provider', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Nx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'cloud.region', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'OB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'destination.ip', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'OR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'dns.question.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Oh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'dns.question.type', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Ox09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.category', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'PB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.dataset', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'PR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.module', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Ph09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.outcome', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Px09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.Ext.original.path', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'QB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.hash.sha256', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'QR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Qh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.path', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Qx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'group.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'RB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'group.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'RR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.asset.criticality', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Rh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.name', + allowed: true, + anonymized: true, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Rx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.os.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'SB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.os.version', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'SR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.risk.calculated_level', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Sh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.risk.calculated_score_norm', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Sx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.original_time', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'TB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.risk_score', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'TR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.description', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Th09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Tx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.references', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'UB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.framework', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'UR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.tactic.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Uh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.tactic.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Ux09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.tactic.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'VB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'VR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Vh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Vx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.subtechnique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'WB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.subtechnique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'WR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.subtechnique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Wh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.severity', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Wx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.workflow_status', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'XB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'message', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'XR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'network.protocol', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Xh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.args', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Xx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.exists', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'YB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.signing_id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'YR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.status', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Yh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.subject_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Yx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.trusted', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ZB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.command_line', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ZR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.executable', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Zh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.exit_code', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Zx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.bytes_compressed_present', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'aB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.malware_signature.all_names', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'aR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.malware_signature.primary.matches', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ah09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.malware_signature.primary.signature.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ax09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.token.integrity_level_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.hash.md5', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.hash.sha1', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.hash.sha256', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'cB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.args', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'cR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.args_count', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ch09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.exists', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'cx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.status', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.subject_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.trusted', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.command_line', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.executable', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'eB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'eR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.pe.original_file_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'eh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.pid', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ex09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.working_directory', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.feature', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.data', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.entropy', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.extension', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.metrics', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.operation', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.path', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.score', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.version', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'rule.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'rule.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'source.ip', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'iB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.framework', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'iR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.tactic.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ih09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.tactic.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ix09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.tactic.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.subtechnique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.subtechnique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.subtechnique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.asset.criticality', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.domain', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'lB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.name', + allowed: true, + anonymized: true, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'lR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.risk.calculated_level', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'lh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.risk.calculated_score_norm', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, +]; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts new file mode 100644 index 0000000000000..93d442bad5e9b --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { ExampleInput, ExampleInputWithOverrides } from '.'; + +const validInput = { + attackDiscoveries: null, + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [{ pageContent: 'content', metadata: { key: 'value' } }], + combinedGenerations: 'gen1gen2', + combinedRefinements: 'ref1ref2', + errors: ['error1', 'error2'], + generationAttempts: 1, + generations: ['gen1', 'gen2'], + hallucinationFailures: 0, + maxGenerationAttempts: 5, + maxHallucinationFailures: 2, + maxRepeatedGenerations: 3, + refinements: ['ref1', 'ref2'], + refinePrompt: 'refine prompt', + replacements: { key: 'replacement' }, + unrefinedResults: null, +}; + +describe('ExampleInput Schema', () => { + it('validates a correct ExampleInput object', () => { + expect(() => ExampleInput.parse(validInput)).not.toThrow(); + }); + + it('throws given an invalid ExampleInput', () => { + const invalidInput = { + attackDiscoveries: 'invalid', // should be an array or null + }; + + expect(() => ExampleInput.parse(invalidInput)).toThrow(); + }); + + it('removes unknown properties', () => { + const hasUnknownProperties = { + ...validInput, + unknownProperty: 'unknown', // <-- should be removed + }; + + const parsed = ExampleInput.parse(hasUnknownProperties); + + expect(parsed).not.toHaveProperty('unknownProperty'); + }); +}); + +describe('ExampleInputWithOverrides Schema', () => { + it('validates a correct ExampleInputWithOverrides object', () => { + const validInputWithOverrides = { + ...validInput, + overrides: { + attackDiscoveryPrompt: 'ad prompt override', + refinePrompt: 'refine prompt override', + }, + }; + + expect(() => ExampleInputWithOverrides.parse(validInputWithOverrides)).not.toThrow(); + }); + + it('throws when given an invalid ExampleInputWithOverrides object', () => { + const invalidInputWithOverrides = { + attackDiscoveries: null, + overrides: 'invalid', // should be an object + }; + + expect(() => ExampleInputWithOverrides.parse(invalidInputWithOverrides)).toThrow(); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts new file mode 100644 index 0000000000000..8183695fd7d2f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts @@ -0,0 +1,52 @@ +/* + * 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 { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import { z } from '@kbn/zod'; + +const Document = z.object({ + pageContent: z.string(), + metadata: z.record(z.string(), z.any()), +}); + +type Document = z.infer; + +/** + * Parses the input from an example in a LangSmith dataset + */ +export const ExampleInput = z.object({ + attackDiscoveries: z.array(AttackDiscovery).nullable().optional(), + attackDiscoveryPrompt: z.string().optional(), + anonymizedAlerts: z.array(Document).optional(), + combinedGenerations: z.string().optional(), + combinedRefinements: z.string().optional(), + errors: z.array(z.string()).optional(), + generationAttempts: z.number().optional(), + generations: z.array(z.string()).optional(), + hallucinationFailures: z.number().optional(), + maxGenerationAttempts: z.number().optional(), + maxHallucinationFailures: z.number().optional(), + maxRepeatedGenerations: z.number().optional(), + refinements: z.array(z.string()).optional(), + refinePrompt: z.string().optional(), + replacements: Replacements.optional(), + unrefinedResults: z.array(AttackDiscovery).nullable().optional(), +}); + +export type ExampleInput = z.infer; + +/** + * The optional overrides for an example input + */ +export const ExampleInputWithOverrides = z.intersection( + ExampleInput, + z.object({ + overrides: ExampleInput.optional(), + }) +); + +export type ExampleInputWithOverrides = z.infer; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts new file mode 100644 index 0000000000000..8ea30103c0768 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { getDefaultPromptTemplate } from '.'; + +describe('getDefaultPromptTemplate', () => { + it('returns the expected prompt template', () => { + const expectedTemplate = `Evaluate based on how well the following submission follows the specified rubric. Grade only based on the rubric and "expected response": + +[BEGIN rubric] +1. Is the submission non-empty and not null? +2. Is the submission well-formed JSON? +3. Evaluate the value of the "detailsMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "detailsMarkdown" in the submission capture the essence of the "expected response", regardless of the order in which they appear, and highlight the same incident(s)? +4. Evaluate the value of the "entitySummaryMarkdown" field of all the "attackDiscoveries" in the submission json. Does the value of "entitySummaryMarkdown" in the submission mention at least 50% the same entities as in the "expected response"? +5. Evaluate the value of the "summaryMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "summaryMarkdown" in the submission at least partially similar to that of the "expected response", regardless of the order in which they appear, and summarize the same incident(s)? +6. Evaluate the value of the "title" field of all the "attackDiscoveries" in the submission json. Are the "title" values in the submission at least partially similar to the tile(s) of the "expected response", regardless of the order in which they appear, and mention the same incident(s)? +7. Evaluate the value of the "alertIds" field of all the "attackDiscoveries" in the submission json. Do they match at least 100% of the "alertIds" in the submission? +[END rubric] + +[BEGIN DATA] +{input} +[BEGIN submission] +{output} +[END submission] +[BEGIN expected response] +{reference} +[END expected response] +[END DATA] + +{criteria} Base your answer based on all the grading rubric items. If at least 5 of the 7 rubric items are correct, consider the submission correct. Write out your explanation for each criterion in the rubric, first in detail, then as a separate summary on a new line. + +Then finally respond with a single character, 'Y' or 'N', on a new line without any preceding or following characters. It's important that only a single character appears on the last line.`; + + const result = getDefaultPromptTemplate(); + + expect(result).toBe(expectedTemplate); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts new file mode 100644 index 0000000000000..08e10f00e7f77 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultPromptTemplate = + () => `Evaluate based on how well the following submission follows the specified rubric. Grade only based on the rubric and "expected response": + +[BEGIN rubric] +1. Is the submission non-empty and not null? +2. Is the submission well-formed JSON? +3. Evaluate the value of the "detailsMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "detailsMarkdown" in the submission capture the essence of the "expected response", regardless of the order in which they appear, and highlight the same incident(s)? +4. Evaluate the value of the "entitySummaryMarkdown" field of all the "attackDiscoveries" in the submission json. Does the value of "entitySummaryMarkdown" in the submission mention at least 50% the same entities as in the "expected response"? +5. Evaluate the value of the "summaryMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "summaryMarkdown" in the submission at least partially similar to that of the "expected response", regardless of the order in which they appear, and summarize the same incident(s)? +6. Evaluate the value of the "title" field of all the "attackDiscoveries" in the submission json. Are the "title" values in the submission at least partially similar to the tile(s) of the "expected response", regardless of the order in which they appear, and mention the same incident(s)? +7. Evaluate the value of the "alertIds" field of all the "attackDiscoveries" in the submission json. Do they match at least 100% of the "alertIds" in the submission? +[END rubric] + +[BEGIN DATA] +{input} +[BEGIN submission] +{output} +[END submission] +[BEGIN expected response] +{reference} +[END expected response] +[END DATA] + +{criteria} Base your answer based on all the grading rubric items. If at least 5 of the 7 rubric items are correct, consider the submission correct. Write out your explanation for each criterion in the rubric, first in detail, then as a separate summary on a new line. + +Then finally respond with a single character, 'Y' or 'N', on a new line without any preceding or following characters. It's important that only a single character appears on the last line.`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts new file mode 100644 index 0000000000000..c261f151b99ab --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts @@ -0,0 +1,125 @@ +/* + * 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/fp'; + +import { getExampleAttackDiscoveriesWithReplacements } from '.'; +import { exampleWithReplacements } from '../../../__mocks__/mock_examples'; + +describe('getExampleAttackDiscoveriesWithReplacements', () => { + it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { + const result = getExampleAttackDiscoveriesWithReplacements(exampleWithReplacements); + + expect(result).toEqual([ + { + title: 'Critical Malware and Phishing Alerts on host SRVMAC08', + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + timestamp: '2024-10-10T22:59:52.749Z', + detailsMarkdown: + '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name SRVMAC08 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name james }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', + summaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', + mitreAttackTactics: ['Credential Access', 'Input Capture'], + entitySummaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}.', + }, + ]); + }); + + it('returns an empty entitySummaryMarkdown when the entitySummaryMarkdown is missing', () => { + const missingEntitySummaryMarkdown = omit( + 'entitySummaryMarkdown', + exampleWithReplacements.outputs?.attackDiscoveries?.[0] + ); + + const exampleWithMissingEntitySummaryMarkdown = { + ...exampleWithReplacements, + outputs: { + ...exampleWithReplacements.outputs, + attackDiscoveries: [missingEntitySummaryMarkdown], + }, + }; + + const result = getExampleAttackDiscoveriesWithReplacements( + exampleWithMissingEntitySummaryMarkdown + ); + + expect(result).toEqual([ + { + title: 'Critical Malware and Phishing Alerts on host SRVMAC08', + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + timestamp: '2024-10-10T22:59:52.749Z', + detailsMarkdown: + '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name SRVMAC08 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name james }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', + summaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', + mitreAttackTactics: ['Credential Access', 'Input Capture'], + entitySummaryMarkdown: '', + }, + ]); + }); + + it('throws when an example is undefined', () => { + expect(() => getExampleAttackDiscoveriesWithReplacements(undefined)).toThrowError(); + }); + + it('throws when the example is missing attackDiscoveries', () => { + const missingAttackDiscoveries = { + ...exampleWithReplacements, + outputs: { + replacements: { ...exampleWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => + getExampleAttackDiscoveriesWithReplacements(missingAttackDiscoveries) + ).toThrowError(); + }); + + it('throws when attackDiscoveries is null', () => { + const nullAttackDiscoveries = { + ...exampleWithReplacements, + outputs: { + attackDiscoveries: null, + replacements: { ...exampleWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => getExampleAttackDiscoveriesWithReplacements(nullAttackDiscoveries)).toThrowError(); + }); + + it('returns the original attack discoveries when replacements are missing', () => { + const missingReplacements = { + ...exampleWithReplacements, + outputs: { + attackDiscoveries: [...exampleWithReplacements.outputs?.attackDiscoveries], + }, + }; + + const result = getExampleAttackDiscoveriesWithReplacements(missingReplacements); + + expect(result).toEqual(exampleWithReplacements.outputs?.attackDiscoveries); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts new file mode 100644 index 0000000000000..8fc5de2a08ed1 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.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 { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; +import type { Example } from 'langsmith/schemas'; + +import { getDiscoveriesWithOriginalValues } from '../../get_discoveries_with_original_values'; + +export const getExampleAttackDiscoveriesWithReplacements = ( + example: Example | undefined +): AttackDiscoveries => { + const exampleAttackDiscoveries = example?.outputs?.attackDiscoveries; + const exampleReplacements = example?.outputs?.replacements ?? {}; + + // NOTE: calls to `parse` throw an error if the Example input is invalid + const validatedAttackDiscoveries = AttackDiscoveries.parse(exampleAttackDiscoveries); + const validatedReplacements = Replacements.parse(exampleReplacements); + + const withReplacements = getDiscoveriesWithOriginalValues({ + attackDiscoveries: validatedAttackDiscoveries, + replacements: validatedReplacements, + }); + + return withReplacements; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts new file mode 100644 index 0000000000000..bd22e5d952b07 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts @@ -0,0 +1,117 @@ +/* + * 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/fp'; + +import { getRunAttackDiscoveriesWithReplacements } from '.'; +import { runWithReplacements } from '../../../__mocks__/mock_runs'; + +describe('getRunAttackDiscoveriesWithReplacements', () => { + it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { + const result = getRunAttackDiscoveriesWithReplacements(runWithReplacements); + + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: + 'The host `{{ host.name SRVMAC08 }}` and user `{{ user.name james }}` were involved in the attack.', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); + + it("returns an empty entitySummaryMarkdown when it's missing from the attack discovery", () => { + const missingEntitySummaryMarkdown = omit( + 'entitySummaryMarkdown', + runWithReplacements.outputs?.attackDiscoveries?.[0] + ); + + const runWithMissingEntitySummaryMarkdown = { + ...runWithReplacements, + outputs: { + ...runWithReplacements.outputs, + attackDiscoveries: [missingEntitySummaryMarkdown], + }, + }; + + const result = getRunAttackDiscoveriesWithReplacements(runWithMissingEntitySummaryMarkdown); + + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: '', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); + + it('throws when the run is missing attackDiscoveries', () => { + const missingAttackDiscoveries = { + ...runWithReplacements, + outputs: { + replacements: { ...runWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => getRunAttackDiscoveriesWithReplacements(missingAttackDiscoveries)).toThrowError(); + }); + + it('throws when attackDiscoveries is null', () => { + const nullAttackDiscoveries = { + ...runWithReplacements, + outputs: { + attackDiscoveries: null, + replacements: { ...runWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => getRunAttackDiscoveriesWithReplacements(nullAttackDiscoveries)).toThrowError(); + }); + + it('returns the original attack discoveries when replacements are missing', () => { + const missingReplacements = { + ...runWithReplacements, + outputs: { + attackDiscoveries: [...runWithReplacements.outputs?.attackDiscoveries], + }, + }; + + const result = getRunAttackDiscoveriesWithReplacements(missingReplacements); + + expect(result).toEqual(runWithReplacements.outputs?.attackDiscoveries); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts new file mode 100644 index 0000000000000..01193320f712b --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; +import type { Run } from 'langsmith/schemas'; + +import { getDiscoveriesWithOriginalValues } from '../../get_discoveries_with_original_values'; + +export const getRunAttackDiscoveriesWithReplacements = (run: Run): AttackDiscoveries => { + const runAttackDiscoveries = run.outputs?.attackDiscoveries; + const runReplacements = run.outputs?.replacements ?? {}; + + // NOTE: calls to `parse` throw an error if the Run Input is invalid + const validatedAttackDiscoveries = AttackDiscoveries.parse(runAttackDiscoveries); + const validatedReplacements = Replacements.parse(runReplacements); + + const withReplacements = getDiscoveriesWithOriginalValues({ + attackDiscoveries: validatedAttackDiscoveries, + replacements: validatedReplacements, + }); + + return withReplacements; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts new file mode 100644 index 0000000000000..829e27df73f14 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts @@ -0,0 +1,98 @@ +/* + * 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 { PromptTemplate } from '@langchain/core/prompts'; +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import { loadEvaluator } from 'langchain/evaluation'; + +import { type GetCustomEvaluatorOptions, getCustomEvaluator } from '.'; +import { getDefaultPromptTemplate } from './get_default_prompt_template'; +import { getExampleAttackDiscoveriesWithReplacements } from './get_example_attack_discoveries_with_replacements'; +import { getRunAttackDiscoveriesWithReplacements } from './get_run_attack_discoveries_with_replacements'; +import { exampleWithReplacements } from '../../__mocks__/mock_examples'; +import { runWithReplacements } from '../../__mocks__/mock_runs'; + +const mockLlm = jest.fn() as unknown as ActionsClientLlm; + +jest.mock('langchain/evaluation', () => ({ + ...jest.requireActual('langchain/evaluation'), + loadEvaluator: jest.fn().mockResolvedValue({ + evaluateStrings: jest.fn().mockResolvedValue({ + key: 'correctness', + score: 0.9, + }), + }), +})); + +const options: GetCustomEvaluatorOptions = { + criteria: 'correctness', + key: 'attack_discovery_correctness', + llm: mockLlm, + template: getDefaultPromptTemplate(), +}; + +describe('getCustomEvaluator', () => { + beforeEach(() => jest.clearAllMocks()); + + it('returns an evaluator function', () => { + const evaluator = getCustomEvaluator(options); + + expect(typeof evaluator).toBe('function'); + }); + + it('calls loadEvaluator with the expected arguments', async () => { + const evaluator = getCustomEvaluator(options); + + await evaluator(runWithReplacements, exampleWithReplacements); + + expect(loadEvaluator).toHaveBeenCalledWith('labeled_criteria', { + criteria: options.criteria, + chainOptions: { + prompt: PromptTemplate.fromTemplate(options.template), + }, + llm: mockLlm, + }); + }); + + it('calls evaluateStrings with the expected arguments', async () => { + const mockEvaluateStrings = jest.fn().mockResolvedValue({ + key: 'correctness', + score: 0.9, + }); + + (loadEvaluator as jest.Mock).mockResolvedValue({ + evaluateStrings: mockEvaluateStrings, + }); + + const evaluator = getCustomEvaluator(options); + + await evaluator(runWithReplacements, exampleWithReplacements); + + const prediction = getRunAttackDiscoveriesWithReplacements(runWithReplacements); + const reference = getExampleAttackDiscoveriesWithReplacements(exampleWithReplacements); + + expect(mockEvaluateStrings).toHaveBeenCalledWith({ + input: '', + prediction: JSON.stringify(prediction, null, 2), + reference: JSON.stringify(reference, null, 2), + }); + }); + + it('returns the expected result', async () => { + const evaluator = getCustomEvaluator(options); + + const result = await evaluator(runWithReplacements, exampleWithReplacements); + + expect(result).toEqual({ key: 'attack_discovery_correctness', score: 0.9 }); + }); + + it('throws given an undefined example', async () => { + const evaluator = getCustomEvaluator(options); + + await expect(async () => evaluator(runWithReplacements, undefined)).rejects.toThrow(); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts new file mode 100644 index 0000000000000..bcabe410c1b72 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import { PromptTemplate } from '@langchain/core/prompts'; +import type { EvaluationResult } from 'langsmith/evaluation'; +import type { Run, Example } from 'langsmith/schemas'; +import { CriteriaLike, loadEvaluator } from 'langchain/evaluation'; + +import { getExampleAttackDiscoveriesWithReplacements } from './get_example_attack_discoveries_with_replacements'; +import { getRunAttackDiscoveriesWithReplacements } from './get_run_attack_discoveries_with_replacements'; + +export interface GetCustomEvaluatorOptions { + /** + * Examples: + * - "conciseness" + * - "relevance" + * - "correctness" + * - "detail" + */ + criteria: CriteriaLike; + /** + * The evaluation score will use this key + */ + key: string; + /** + * LLm to use for evaluation + */ + llm: ActionsClientLlm; + /** + * A prompt template that uses the {input}, {submission}, and {reference} variables + */ + template: string; +} + +export type CustomEvaluator = ( + rootRun: Run, + example: Example | undefined +) => Promise; + +export const getCustomEvaluator = + ({ criteria, key, llm, template }: GetCustomEvaluatorOptions): CustomEvaluator => + async (rootRun, example) => { + const chain = await loadEvaluator('labeled_criteria', { + criteria, + chainOptions: { + prompt: PromptTemplate.fromTemplate(template), + }, + llm, + }); + + const exampleAttackDiscoveriesWithReplacements = + getExampleAttackDiscoveriesWithReplacements(example); + + const runAttackDiscoveriesWithReplacements = getRunAttackDiscoveriesWithReplacements(rootRun); + + // NOTE: res contains a score, as well as the reasoning for the score + const res = await chain.evaluateStrings({ + input: '', // empty for now, but this could be the alerts, i.e. JSON.stringify(rootRun.outputs?.anonymizedAlerts, null, 2), + prediction: JSON.stringify(runAttackDiscoveriesWithReplacements, null, 2), + reference: JSON.stringify(exampleAttackDiscoveriesWithReplacements, null, 2), + }); + + return { key, score: res.score }; + }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts new file mode 100644 index 0000000000000..423248aa5c3d6 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { AttackDiscovery } from '@kbn/elastic-assistant-common'; +import { omit } from 'lodash/fp'; + +import { getDiscoveriesWithOriginalValues } from '.'; +import { runWithReplacements } from '../../__mocks__/mock_runs'; + +describe('getDiscoveriesWithOriginalValues', () => { + it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { + const result = getDiscoveriesWithOriginalValues({ + attackDiscoveries: runWithReplacements.outputs?.attackDiscoveries, + replacements: runWithReplacements.outputs?.replacements, + }); + + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: + 'The host `{{ host.name SRVMAC08 }}` and user `{{ user.name james }}` were involved in the attack.', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); + + it("returns an empty entitySummaryMarkdown when it's missing from the attack discovery", () => { + const missingEntitySummaryMarkdown = omit( + 'entitySummaryMarkdown', + runWithReplacements.outputs?.attackDiscoveries?.[0] + ) as unknown as AttackDiscovery; + + const result = getDiscoveriesWithOriginalValues({ + attackDiscoveries: [missingEntitySummaryMarkdown], + replacements: runWithReplacements.outputs?.replacements, + }); + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: '', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts new file mode 100644 index 0000000000000..1ef88e2208d1f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + type AttackDiscovery, + Replacements, + replaceAnonymizedValuesWithOriginalValues, +} from '@kbn/elastic-assistant-common'; + +export const getDiscoveriesWithOriginalValues = ({ + attackDiscoveries, + replacements, +}: { + attackDiscoveries: AttackDiscovery[]; + replacements: Replacements; +}): AttackDiscovery[] => + attackDiscoveries.map((attackDiscovery) => ({ + ...attackDiscovery, + detailsMarkdown: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.detailsMarkdown, + replacements, + }), + entitySummaryMarkdown: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.entitySummaryMarkdown ?? '', + replacements, + }), + summaryMarkdown: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.summaryMarkdown, + replacements, + }), + title: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.title, + replacements, + }), + })); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts new file mode 100644 index 0000000000000..132a819d44ec8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { loggerMock } from '@kbn/logging-mocks'; + +import { getEvaluatorLlm } from '.'; + +jest.mock('@kbn/langchain/server', () => ({ + ...jest.requireActual('@kbn/langchain/server'), + + ActionsClientLlm: jest.fn(), +})); + +const connectorTimeout = 1000; + +const evaluatorConnectorId = 'evaluator-connector-id'; +const evaluatorConnector = { + id: 'evaluatorConnectorId', + actionTypeId: '.gen-ai', + name: 'GPT-4o', + isPreconfigured: true, + isSystemAction: false, + isDeprecated: false, +} as Connector; + +const experimentConnector: Connector = { + name: 'Gemini 1.5 Pro 002', + actionTypeId: '.gemini', + config: { + apiUrl: 'https://example.com', + defaultModel: 'gemini-1.5-pro-002', + gcpRegion: 'test-region', + gcpProjectID: 'test-project-id', + }, + secrets: { + credentialsJson: '{}', + }, + id: 'gemini-1-5-pro-002', + isPreconfigured: true, + isSystemAction: false, + isDeprecated: false, +} as Connector; + +const logger = loggerMock.create(); + +describe('getEvaluatorLlm', () => { + beforeEach(() => jest.clearAllMocks()); + + describe('getting the evaluation connector', () => { + it("calls actionsClient.get with the evaluator connector ID when it's provided", async () => { + const actionsClient = { + get: jest.fn(), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(actionsClient.get).toHaveBeenCalledWith({ + id: evaluatorConnectorId, + throwIfSystemAction: false, + }); + }); + + it("calls actionsClient.get with the experiment connector ID when the evaluator connector ID isn't provided", async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(null), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId: undefined, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(actionsClient.get).toHaveBeenCalledWith({ + id: experimentConnector.id, + throwIfSystemAction: false, + }); + }); + + it('falls back to the experiment connector when the evaluator connector is not found', async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(null), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(ActionsClientLlm).toHaveBeenCalledWith( + expect.objectContaining({ + connectorId: experimentConnector.id, + }) + ); + }); + }); + + it('logs the expected connector names and types', async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(evaluatorConnector), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(logger.info).toHaveBeenCalledWith( + `The ${evaluatorConnector.name} (openai) connector will judge output from the ${experimentConnector.name} (gemini) connector` + ); + }); + + it('creates a new ActionsClientLlm instance with the expected traceOptions', async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(evaluatorConnector), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: 'test-api-key', + logger, + }); + + expect(ActionsClientLlm).toHaveBeenCalledWith( + expect.objectContaining({ + traceOptions: { + projectName: 'evaluators', + tracers: expect.any(Array), + }, + }) + ); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts new file mode 100644 index 0000000000000..236def9670d07 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { Logger } from '@kbn/core/server'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; + +import { getLlmType } from '../../../../../routes/utils'; + +export const getEvaluatorLlm = async ({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey, + logger, +}: { + actionsClient: PublicMethodsOf; + connectorTimeout: number; + evaluatorConnectorId: string | undefined; + experimentConnector: Connector; + langSmithApiKey: string | undefined; + logger: Logger; +}): Promise => { + const evaluatorConnector = + (await actionsClient.get({ + id: evaluatorConnectorId ?? experimentConnector.id, // fallback to the experiment connector if the evaluator connector is not found: + throwIfSystemAction: false, + })) ?? experimentConnector; + + const evaluatorLlmType = getLlmType(evaluatorConnector.actionTypeId); + const experimentLlmType = getLlmType(experimentConnector.actionTypeId); + + logger.info( + `The ${evaluatorConnector.name} (${evaluatorLlmType}) connector will judge output from the ${experimentConnector.name} (${experimentLlmType}) connector` + ); + + const traceOptions = { + projectName: 'evaluators', + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: 'evaluators', + logger, + }), + ], + }; + + return new ActionsClientLlm({ + actionsClient, + connectorId: evaluatorConnector.id, + llmType: evaluatorLlmType, + logger, + temperature: 0, // zero temperature for evaluation + timeout: connectorTimeout, + traceOptions, + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts new file mode 100644 index 0000000000000..47f36bc6fb0e7 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts @@ -0,0 +1,121 @@ +/* + * 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/fp'; +import type { Example } from 'langsmith/schemas'; + +import { getGraphInputOverrides } from '.'; +import { exampleWithReplacements } from '../../__mocks__/mock_examples'; + +const exampleWithAlerts: Example = { + ...exampleWithReplacements, + outputs: { + ...exampleWithReplacements.outputs, + anonymizedAlerts: [ + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + ], + }, +}; + +const exampleWithNoReplacements: Example = { + ...exampleWithReplacements, + outputs: { + ...omit('replacements', exampleWithReplacements.outputs), + }, +}; + +describe('getGraphInputOverrides', () => { + describe('root-level outputs overrides', () => { + it('returns the anonymizedAlerts from the root level of the outputs when present', () => { + const overrides = getGraphInputOverrides(exampleWithAlerts.outputs); + + expect(overrides.anonymizedAlerts).toEqual(exampleWithAlerts.outputs?.anonymizedAlerts); + }); + + it('does NOT populate the anonymizedAlerts key when it does NOT exist in the outputs', () => { + const overrides = getGraphInputOverrides(exampleWithReplacements.outputs); + + expect(overrides).not.toHaveProperty('anonymizedAlerts'); + }); + + it('returns replacements from the root level of the outputs when present', () => { + const overrides = getGraphInputOverrides(exampleWithReplacements.outputs); + + expect(overrides.replacements).toEqual(exampleWithReplacements.outputs?.replacements); + }); + + it('does NOT populate the replacements key when it does NOT exist in the outputs', () => { + const overrides = getGraphInputOverrides(exampleWithNoReplacements.outputs); + + expect(overrides).not.toHaveProperty('replacements'); + }); + + it('removes unknown properties', () => { + const withUnknownProperties = { + ...exampleWithReplacements, + outputs: { + ...exampleWithReplacements.outputs, + unknownProperty: 'unknown', + }, + }; + + const overrides = getGraphInputOverrides(withUnknownProperties.outputs); + + expect(overrides).not.toHaveProperty('unknownProperty'); + }); + }); + + describe('overrides', () => { + it('returns all overrides at the root level', () => { + const exampleWithOverrides = { + ...exampleWithAlerts, + outputs: { + ...exampleWithAlerts.outputs, + overrides: { + attackDiscoveries: [], + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [], + combinedGenerations: 'combinedGenerations', + combinedRefinements: 'combinedRefinements', + errors: ['error'], + generationAttempts: 1, + generations: ['generation'], + hallucinationFailures: 2, + maxGenerationAttempts: 3, + maxHallucinationFailures: 4, + maxRepeatedGenerations: 5, + refinements: ['refinement'], + refinePrompt: 'refinePrompt', + replacements: {}, + unrefinedResults: [], + }, + }, + }; + + const overrides = getGraphInputOverrides(exampleWithOverrides.outputs); + + expect(overrides).toEqual({ + ...exampleWithOverrides.outputs?.overrides, + }); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts new file mode 100644 index 0000000000000..232218f4386f8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.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 { pick } from 'lodash/fp'; + +import { ExampleInputWithOverrides } from '../../example_input'; +import { GraphState } from '../../../graphs/default_attack_discovery_graph/types'; + +/** + * Parses input from an LangSmith dataset example to get the graph input overrides + */ +export const getGraphInputOverrides = (outputs: unknown): Partial => { + const validatedInput = ExampleInputWithOverrides.safeParse(outputs).data ?? {}; // safeParse removes unknown properties + + const { overrides } = validatedInput; + + // return all overrides at the root level: + return { + // pick extracts just the anonymizedAlerts and replacements from the root level of the input, + // and only adds the anonymizedAlerts key if it exists in the input + ...pick('anonymizedAlerts', validatedInput), + ...pick('replacements', validatedInput), + ...overrides, // bring all other overrides to the root level + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts new file mode 100644 index 0000000000000..40b0f080fe54a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger } from '@kbn/core/server'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import { asyncForEach } from '@kbn/std'; +import { PublicMethodsOf } from '@kbn/utility-types'; + +import { DEFAULT_EVAL_ANONYMIZATION_FIELDS } from './constants'; +import { AttackDiscoveryGraphMetadata } from '../../langchain/graphs'; +import { DefaultAttackDiscoveryGraph } from '../graphs/default_attack_discovery_graph'; +import { getLlmType } from '../../../routes/utils'; +import { runEvaluations } from './run_evaluations'; + +export const evaluateAttackDiscovery = async ({ + actionsClient, + attackDiscoveryGraphs, + alertsIndexPattern, + anonymizationFields = DEFAULT_EVAL_ANONYMIZATION_FIELDS, // determines which fields are included in the alerts + connectors, + connectorTimeout, + datasetName, + esClient, + evaluationId, + evaluatorConnectorId, + langSmithApiKey, + langSmithProject, + logger, + runName, + size, +}: { + actionsClient: PublicMethodsOf; + attackDiscoveryGraphs: AttackDiscoveryGraphMetadata[]; + alertsIndexPattern: string; + anonymizationFields?: AnonymizationFieldResponse[]; + connectors: Connector[]; + connectorTimeout: number; + datasetName: string; + esClient: ElasticsearchClient; + evaluationId: string; + evaluatorConnectorId: string | undefined; + langSmithApiKey: string | undefined; + langSmithProject: string | undefined; + logger: Logger; + runName: string; + size: number; +}): Promise => { + await asyncForEach(attackDiscoveryGraphs, async ({ getDefaultAttackDiscoveryGraph }) => { + // create a graph for every connector: + const graphs: Array<{ + connector: Connector; + graph: DefaultAttackDiscoveryGraph; + llmType: string | undefined; + name: string; + traceOptions: { + projectName: string | undefined; + tracers: LangChainTracer[]; + }; + }> = connectors.map((connector) => { + const llmType = getLlmType(connector.actionTypeId); + + const traceOptions = { + projectName: langSmithProject, + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: langSmithProject, + logger, + }), + ], + }; + + const llm = new ActionsClientLlm({ + actionsClient, + connectorId: connector.id, + llmType, + logger, + temperature: 0, // zero temperature for attack discovery, because we want structured JSON output + timeout: connectorTimeout, + traceOptions, + }); + + const graph = getDefaultAttackDiscoveryGraph({ + alertsIndexPattern, + anonymizationFields, + esClient, + llm, + logger, + size, + }); + + return { + connector, + graph, + llmType, + name: `${runName} - ${connector.name} - ${evaluationId} - Attack discovery`, + traceOptions, + }; + }); + + // run the evaluations for each graph: + await runEvaluations({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + datasetName, + graphs, + langSmithApiKey, + logger, + }); + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts new file mode 100644 index 0000000000000..19eb99d57c84c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { Logger } from '@kbn/core/server'; +import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; +import { asyncForEach } from '@kbn/std'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { Client } from 'langsmith'; +import { evaluate } from 'langsmith/evaluation'; + +import { getEvaluatorLlm } from '../helpers/get_evaluator_llm'; +import { getCustomEvaluator } from '../helpers/get_custom_evaluator'; +import { getDefaultPromptTemplate } from '../helpers/get_custom_evaluator/get_default_prompt_template'; +import { getGraphInputOverrides } from '../helpers/get_graph_input_overrides'; +import { DefaultAttackDiscoveryGraph } from '../../graphs/default_attack_discovery_graph'; +import { GraphState } from '../../graphs/default_attack_discovery_graph/types'; + +/** + * Runs an evaluation for each graph so they show up separately (resulting in + * each dataset run grouped by connector) + */ +export const runEvaluations = async ({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + datasetName, + graphs, + langSmithApiKey, + logger, +}: { + actionsClient: PublicMethodsOf; + connectorTimeout: number; + evaluatorConnectorId: string | undefined; + datasetName: string; + graphs: Array<{ + connector: Connector; + graph: DefaultAttackDiscoveryGraph; + llmType: string | undefined; + name: string; + traceOptions: { + projectName: string | undefined; + tracers: LangChainTracer[]; + }; + }>; + langSmithApiKey: string | undefined; + logger: Logger; +}): Promise => + asyncForEach(graphs, async ({ connector, graph, llmType, name, traceOptions }) => { + const subject = `connector "${connector.name}" (${llmType}), running experiment "${name}"`; + + try { + logger.info( + () => + `Evaluating ${subject} with dataset "${datasetName}" and evaluator "${evaluatorConnectorId}"` + ); + + const predict = async (input: unknown): Promise => { + logger.debug(() => `Raw example Input for ${subject}":\n ${input}`); + + // The example `Input` may have overrides for the initial state of the graph: + const overrides = getGraphInputOverrides(input); + + return graph.invoke( + { + ...overrides, + }, + { + callbacks: [...(traceOptions.tracers ?? [])], + runName: name, + tags: ['evaluation', llmType ?? ''], + } + ); + }; + + const llm = await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector: connector, + langSmithApiKey, + logger, + }); + + const customEvaluator = getCustomEvaluator({ + criteria: 'correctness', + key: 'attack_discovery_correctness', + llm, + template: getDefaultPromptTemplate(), + }); + + const evalOutput = await evaluate(predict, { + client: new Client({ apiKey: langSmithApiKey }), + data: datasetName ?? '', + evaluators: [customEvaluator], + experimentPrefix: name, + maxConcurrency: 5, // prevents rate limiting + }); + + logger.info(() => `Evaluation complete for ${subject}`); + + logger.debug( + () => `Evaluation output for ${subject}:\n ${JSON.stringify(evalOutput, null, 2)}` + ); + } catch (e) { + logger.error(`Error evaluating ${subject}: ${e}`); + } + }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts new file mode 100644 index 0000000000000..fb5df8f26d0c2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// LangGraph metadata +export const ATTACK_DISCOVERY_GRAPH_RUN_NAME = 'Attack discovery'; +export const ATTACK_DISCOVERY_TAG = 'attack-discovery'; + +// Limits +export const DEFAULT_MAX_GENERATION_ATTEMPTS = 10; +export const DEFAULT_MAX_HALLUCINATION_FAILURES = 5; +export const DEFAULT_MAX_REPEATED_GENERATIONS = 3; + +export const NodeType = { + GENERATE_NODE: 'generate', + REFINE_NODE: 'refine', + RETRIEVE_ANONYMIZED_ALERTS_NODE: 'retrieve_anonymized_alerts', +} as const; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts new file mode 100644 index 0000000000000..225c4a2b8935c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.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 { getGenerateOrEndDecision } from '.'; + +describe('getGenerateOrEndDecision', () => { + it('returns "end" when hasZeroAlerts is true', () => { + const result = getGenerateOrEndDecision(true); + + expect(result).toEqual('end'); + }); + + it('returns "generate" when hasZeroAlerts is false', () => { + const result = getGenerateOrEndDecision(false); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts new file mode 100644 index 0000000000000..b134b2f3a6118 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getGenerateOrEndDecision = (hasZeroAlerts: boolean): 'end' | 'generate' => + hasZeroAlerts ? 'end' : 'generate'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts new file mode 100644 index 0000000000000..06dd1529179fa --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { loggerMock } from '@kbn/logging-mocks'; + +import { getGenerateOrEndEdge } from '.'; +import type { GraphState } from '../../types'; + +const logger = loggerMock.create(); + +const graphState: GraphState = { + attackDiscoveries: null, + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [ + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + ], + combinedGenerations: 'generations', + combinedRefinements: 'refinements', + errors: [], + generationAttempts: 0, + generations: [], + hallucinationFailures: 0, + maxGenerationAttempts: 10, + maxHallucinationFailures: 5, + maxRepeatedGenerations: 10, + refinements: [], + refinePrompt: 'refinePrompt', + replacements: {}, + unrefinedResults: null, +}; + +describe('getGenerateOrEndEdge', () => { + beforeEach(() => jest.clearAllMocks()); + + it("returns 'end' when there are zero alerts", () => { + const state: GraphState = { + ...graphState, + anonymizedAlerts: [], // <-- zero alerts + }; + + const edge = getGenerateOrEndEdge(logger); + const result = edge(state); + + expect(result).toEqual('end'); + }); + + it("returns 'generate' when there are alerts", () => { + const edge = getGenerateOrEndEdge(logger); + const result = edge(graphState); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts new file mode 100644 index 0000000000000..5bfc4912298eb --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.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 type { Logger } from '@kbn/core/server'; + +import { getGenerateOrEndDecision } from './helpers/get_generate_or_end_decision'; +import { getHasZeroAlerts } from '../helpers/get_has_zero_alerts'; +import type { GraphState } from '../../types'; + +export const getGenerateOrEndEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'end' | 'generate' => { + logger?.debug(() => '---GENERATE OR END---'); + const { anonymizedAlerts } = state; + + const hasZeroAlerts = getHasZeroAlerts(anonymizedAlerts); + + const decision = getGenerateOrEndDecision(hasZeroAlerts); + + logger?.debug( + () => `generatOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( + { + anonymizedAlerts: anonymizedAlerts.length, + hasZeroAlerts, + }, + null, + 2 + )} +\n---GENERATE OR END: ${decision}---` + ); + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts new file mode 100644 index 0000000000000..42c63b18459ed --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getGenerateOrRefineOrEndDecision } from '.'; + +describe('getGenerateOrRefineOrEndDecision', () => { + it("returns 'end' if getShouldEnd returns true", () => { + const result = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults: false, + hasZeroAlerts: true, + maxHallucinationFailuresReached: true, + maxRetriesReached: true, + }); + + expect(result).toEqual('end'); + }); + + it("returns 'refine' if hasUnrefinedResults is true and getShouldEnd returns false", () => { + const result = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults: true, + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toEqual('refine'); + }); + + it("returns 'generate' if hasUnrefinedResults is false and getShouldEnd returns false", () => { + const result = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults: false, + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts new file mode 100644 index 0000000000000..b409f63f71a69 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { getShouldEnd } from '../get_should_end'; + +export const getGenerateOrRefineOrEndDecision = ({ + hasUnrefinedResults, + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasUnrefinedResults: boolean; + hasZeroAlerts: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): 'end' | 'generate' | 'refine' => { + if (getShouldEnd({ hasZeroAlerts, maxHallucinationFailuresReached, maxRetriesReached })) { + return 'end'; + } else if (hasUnrefinedResults) { + return 'refine'; + } else { + return 'generate'; + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts new file mode 100644 index 0000000000000..82480a6ad6889 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { getShouldEnd } from '.'; + +describe('getShouldEnd', () => { + it('returns true if hasZeroAlerts is true', () => { + const result = getShouldEnd({ + hasZeroAlerts: true, // <-- true + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toBe(true); + }); + + it('returns true if maxHallucinationFailuresReached is true', () => { + const result = getShouldEnd({ + hasZeroAlerts: false, + maxHallucinationFailuresReached: true, // <-- true + maxRetriesReached: false, + }); + + expect(result).toBe(true); + }); + + it('returns true if maxRetriesReached is true', () => { + const result = getShouldEnd({ + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: true, // <-- true + }); + + expect(result).toBe(true); + }); + + it('returns false if all conditions are false', () => { + const result = getShouldEnd({ + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toBe(false); + }); + + it('returns true if all conditions are true', () => { + const result = getShouldEnd({ + hasZeroAlerts: true, + maxHallucinationFailuresReached: true, + maxRetriesReached: true, + }); + + expect(result).toBe(true); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts new file mode 100644 index 0000000000000..9724ba25886fa --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getShouldEnd = ({ + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasZeroAlerts: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): boolean => hasZeroAlerts || maxRetriesReached || maxHallucinationFailuresReached; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts new file mode 100644 index 0000000000000..585a1bc2dcac3 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggerMock } from '@kbn/logging-mocks'; + +import { getGenerateOrRefineOrEndEdge } from '.'; +import type { GraphState } from '../../types'; + +const logger = loggerMock.create(); + +const graphState: GraphState = { + attackDiscoveries: null, + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [ + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + ], + combinedGenerations: '', + combinedRefinements: '', + errors: [], + generationAttempts: 0, + generations: [], + hallucinationFailures: 0, + maxGenerationAttempts: 10, + maxHallucinationFailures: 5, + maxRepeatedGenerations: 3, + refinements: [], + refinePrompt: 'refinePrompt', + replacements: {}, + unrefinedResults: null, +}; + +describe('getGenerateOrRefineOrEndEdge', () => { + beforeEach(() => jest.clearAllMocks()); + + it('returns "end" when there are zero alerts', () => { + const withZeroAlerts: GraphState = { + ...graphState, + anonymizedAlerts: [], // <-- zero alerts + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withZeroAlerts); + + expect(result).toEqual('end'); + }); + + it('returns "end" when max hallucination failures are reached', () => { + const withMaxHallucinationFailures: GraphState = { + ...graphState, + hallucinationFailures: 5, + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withMaxHallucinationFailures); + + expect(result).toEqual('end'); + }); + + it('returns "end" when max retries are reached', () => { + const withMaxRetries: GraphState = { + ...graphState, + generationAttempts: 10, + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withMaxRetries); + + expect(result).toEqual('end'); + }); + + it('returns refine when there are unrefined results', () => { + const withUnrefinedResults: GraphState = { + ...graphState, + unrefinedResults: [ + { + alertIds: [], + id: 'test-id', + detailsMarkdown: 'test-details', + entitySummaryMarkdown: 'test-summary', + summaryMarkdown: 'test-summary', + title: 'test-title', + timestamp: '2024-10-10T21:01:24.148Z', + }, + ], + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withUnrefinedResults); + + expect(result).toEqual('refine'); + }); + + it('return generate when there are no unrefined results', () => { + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(graphState); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts new file mode 100644 index 0000000000000..3368a04ec9204 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import { getGenerateOrRefineOrEndDecision } from './helpers/get_generate_or_refine_or_end_decision'; +import { getHasResults } from '../helpers/get_has_results'; +import { getHasZeroAlerts } from '../helpers/get_has_zero_alerts'; +import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; +import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; +import type { GraphState } from '../../types'; + +export const getGenerateOrRefineOrEndEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'end' | 'generate' | 'refine' => { + logger?.debug(() => '---GENERATE OR REFINE OR END---'); + const { + anonymizedAlerts, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + unrefinedResults, + } = state; + + const hasZeroAlerts = getHasZeroAlerts(anonymizedAlerts); + const hasUnrefinedResults = getHasResults(unrefinedResults); + const maxRetriesReached = getMaxRetriesReached({ generationAttempts, maxGenerationAttempts }); + const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ + hallucinationFailures, + maxHallucinationFailures, + }); + + const decision = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults, + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, + }); + + logger?.debug( + () => + `generatOrRefineOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( + { + anonymizedAlerts: anonymizedAlerts.length, + generationAttempts, + hallucinationFailures, + hasUnrefinedResults, + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, + unrefinedResults: unrefinedResults?.length ?? 0, + }, + null, + 2 + )} + \n---GENERATE OR REFINE OR END: ${decision}---` + ); + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts new file mode 100644 index 0000000000000..413f01b74dece --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AttackDiscovery } from '@kbn/elastic-assistant-common'; + +export const getHasResults = (attackDiscoveries: AttackDiscovery[] | null): boolean => + attackDiscoveries !== null; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts new file mode 100644 index 0000000000000..d768b363f101e --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Document } from '@langchain/core/documents'; +import { isEmpty } from 'lodash/fp'; + +export const getHasZeroAlerts = (anonymizedAlerts: Document[]): boolean => + isEmpty(anonymizedAlerts); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts new file mode 100644 index 0000000000000..7168aa08aeef2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getShouldEnd } from '../get_should_end'; + +export const getRefineOrEndDecision = ({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasFinalResults: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): 'refine' | 'end' => + getShouldEnd({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, + }) + ? 'end' + : 'refine'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts new file mode 100644 index 0000000000000..697f93dd3a02f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getShouldEnd = ({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasFinalResults: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): boolean => hasFinalResults || maxRetriesReached || maxHallucinationFailuresReached; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts new file mode 100644 index 0000000000000..85140dceafdcb --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import { getRefineOrEndDecision } from './helpers/get_refine_or_end_decision'; +import { getHasResults } from '../helpers/get_has_results'; +import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; +import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; +import type { GraphState } from '../../types'; + +export const getRefineOrEndEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'end' | 'refine' => { + logger?.debug(() => '---REFINE OR END---'); + const { + attackDiscoveries, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + } = state; + + const hasFinalResults = getHasResults(attackDiscoveries); + const maxRetriesReached = getMaxRetriesReached({ generationAttempts, maxGenerationAttempts }); + const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ + hallucinationFailures, + maxHallucinationFailures, + }); + + const decision = getRefineOrEndDecision({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, + }); + + logger?.debug( + () => + `refineOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( + { + attackDiscoveries: attackDiscoveries?.length ?? 0, + generationAttempts, + hallucinationFailures, + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, + }, + null, + 2 + )} + \n---REFINE OR END: ${decision}---` + ); + + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts new file mode 100644 index 0000000000000..050ca17484185 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Document } from '@langchain/core/documents'; + +export const getRetrieveOrGenerate = ( + anonymizedAlerts: Document[] +): 'retrieve_anonymized_alerts' | 'generate' => + anonymizedAlerts.length === 0 ? 'retrieve_anonymized_alerts' : 'generate'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts new file mode 100644 index 0000000000000..ad0512497d07d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import { getRetrieveOrGenerate } from './get_retrieve_or_generate'; +import type { GraphState } from '../../types'; + +export const getRetrieveAnonymizedAlertsOrGenerateEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'retrieve_anonymized_alerts' | 'generate' => { + logger?.debug(() => '---RETRIEVE ANONYMIZED ALERTS OR GENERATE---'); + const { anonymizedAlerts } = state; + + const decision = getRetrieveOrGenerate(anonymizedAlerts); + + logger?.debug( + () => + `retrieveAnonymizedAlertsOrGenerateEdge evaluated the following (derived) state:\n${JSON.stringify( + { + anonymizedAlerts: anonymizedAlerts.length, + }, + null, + 2 + )} + \n---RETRIEVE ANONYMIZED ALERTS OR GENERATE: ${decision}---` + ); + + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts new file mode 100644 index 0000000000000..07985381afa73 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getMaxHallucinationFailuresReached = ({ + hallucinationFailures, + maxHallucinationFailures, +}: { + hallucinationFailures: number; + maxHallucinationFailures: number; +}): boolean => hallucinationFailures >= maxHallucinationFailures; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts new file mode 100644 index 0000000000000..c1e36917b45cf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getMaxRetriesReached = ({ + generationAttempts, + maxGenerationAttempts, +}: { + generationAttempts: number; + maxGenerationAttempts: number; +}): boolean => generationAttempts >= maxGenerationAttempts; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts new file mode 100644 index 0000000000000..b2c90636ef523 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { CompiledStateGraph } from '@langchain/langgraph'; +import { END, START, StateGraph } from '@langchain/langgraph'; + +import { NodeType } from './constants'; +import { getGenerateOrEndEdge } from './edges/generate_or_end'; +import { getGenerateOrRefineOrEndEdge } from './edges/generate_or_refine_or_end'; +import { getRefineOrEndEdge } from './edges/refine_or_end'; +import { getRetrieveAnonymizedAlertsOrGenerateEdge } from './edges/retrieve_anonymized_alerts_or_generate'; +import { getDefaultGraphState } from './state'; +import { getGenerateNode } from './nodes/generate'; +import { getRefineNode } from './nodes/refine'; +import { getRetrieveAnonymizedAlertsNode } from './nodes/retriever'; +import type { GraphState } from './types'; + +export interface GetDefaultAttackDiscoveryGraphParams { + alertsIndexPattern?: string; + anonymizationFields: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + llm: ActionsClientLlm; + logger?: Logger; + onNewReplacements?: (replacements: Replacements) => void; + replacements?: Replacements; + size: number; +} + +export type DefaultAttackDiscoveryGraph = ReturnType; + +/** + * This function returns a compiled state graph that represents the default + * Attack discovery graph. + * + * Refer to the following diagram for this graph: + * x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png + */ +export const getDefaultAttackDiscoveryGraph = ({ + alertsIndexPattern, + anonymizationFields, + esClient, + llm, + logger, + onNewReplacements, + replacements, + size, +}: GetDefaultAttackDiscoveryGraphParams): CompiledStateGraph< + GraphState, + Partial, + 'generate' | 'refine' | 'retrieve_anonymized_alerts' | '__start__' +> => { + try { + const graphState = getDefaultGraphState(); + + // get nodes: + const retrieveAnonymizedAlertsNode = getRetrieveAnonymizedAlertsNode({ + alertsIndexPattern, + anonymizationFields, + esClient, + logger, + onNewReplacements, + replacements, + size, + }); + + const generateNode = getGenerateNode({ + llm, + logger, + }); + + const refineNode = getRefineNode({ + llm, + logger, + }); + + // get edges: + const generateOrEndEdge = getGenerateOrEndEdge(logger); + + const generatOrRefineOrEndEdge = getGenerateOrRefineOrEndEdge(logger); + + const refineOrEndEdge = getRefineOrEndEdge(logger); + + const retrieveAnonymizedAlertsOrGenerateEdge = + getRetrieveAnonymizedAlertsOrGenerateEdge(logger); + + // create the graph: + const graph = new StateGraph({ channels: graphState }) + .addNode(NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, retrieveAnonymizedAlertsNode) + .addNode(NodeType.GENERATE_NODE, generateNode) + .addNode(NodeType.REFINE_NODE, refineNode) + .addConditionalEdges(START, retrieveAnonymizedAlertsOrGenerateEdge, { + generate: NodeType.GENERATE_NODE, + retrieve_anonymized_alerts: NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, + }) + .addConditionalEdges(NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, generateOrEndEdge, { + end: END, + generate: NodeType.GENERATE_NODE, + }) + .addConditionalEdges(NodeType.GENERATE_NODE, generatOrRefineOrEndEdge, { + end: END, + generate: NodeType.GENERATE_NODE, + refine: NodeType.REFINE_NODE, + }) + .addConditionalEdges(NodeType.REFINE_NODE, refineOrEndEdge, { + end: END, + refine: NodeType.REFINE_NODE, + }); + + // compile the graph: + return graph.compile(); + } catch (e) { + throw new Error(`Unable to compile AttackDiscoveryGraph\n${e}`); + } +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/mock/mock_anonymization_fields.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields.ts similarity index 100% rename from x-pack/plugins/security_solution/server/assistant/tools/mock/mock_anonymization_fields.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields.ts diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts new file mode 100644 index 0000000000000..ed5549acc586a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const mockEmptyOpenAndAcknowledgedAlertsQueryResults = { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 0, + relation: 'eq', + }, + max_score: null, + hits: [], + }, +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts new file mode 100644 index 0000000000000..3f22f787f54f8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts @@ -0,0 +1,1396 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const mockOpenAndAcknowledgedAlertsQueryResults = { + took: 13, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 31, + relation: 'eq', + }, + max_score: null, + hits: [ + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['85caafe3d324e3287b85348fa2fae492'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!!', + ], + 'process.parent.name': ['unix1'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231', + ], + 'process.code_signature.signing_id': ['nans-55554944e5f232edcf023cf68e8e5dac81584f78'], + 'process.pid': [1227], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': ['/Users/james/unix1'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': ['/Users/james/unix1'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': [ + '/Users/james/unix1', + '/Users/james/library/Keychains/login.keychain-db', + 'TempTemp1234!!', + ], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [3], + 'process.name': ['unix1'], + 'process.parent.args': [ + '/Users/james/unix1', + '/Users/james/library/Keychains/login.keychain-db', + 'TempTemp1234!!', + ], + '@timestamp': ['2024-05-07T12:48:45.032Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': [ + '/Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!!', + ], + 'host.risk.calculated_level': ['High'], + _id: ['b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560'], + 'process.hash.sha1': ['4ca549355736e4af6434efc4ec9a044ceb2ae3c3'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:39.368Z'], + }, + sort: [99, 1715086125032], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1220], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + '@timestamp': ['2024-05-07T12:48:45.030Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'host.risk.calculated_level': ['High'], + _id: ['0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:38.061Z'], + }, + sort: [99, 1715086125030], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['85caafe3d324e3287b85348fa2fae492'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231', + ], + 'process.code_signature.signing_id': ['nans-55554944e5f232edcf023cf68e8e5dac81584f78'], + 'process.pid': [1220], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': ['/Users/james/unix1'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['unix1'], + 'process.parent.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + '@timestamp': ['2024-05-07T12:48:45.029Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'host.risk.calculated_level': ['High'], + _id: ['600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a'], + 'process.hash.sha1': ['4ca549355736e4af6434efc4ec9a044ceb2ae3c3'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:37.881Z'], + }, + sort: [99, 1715086125029], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['3f19892ab44eb9bc7bc03f438944301e'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'f80234ff6fed2c62d23f37443f2412fbe806711b6add2ac126e03e282082c8f5', + ], + 'process.code_signature.signing_id': ['com.apple.chmod'], + 'process.pid': [1219], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Software Signing'], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': ['/bin/chmod'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': ['chmod', '777', '/Users/james/unix1'], + 'process.code_signature.status': ['No error.'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['chmod'], + 'process.parent.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + '@timestamp': ['2024-05-07T12:48:45.028Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['chmod 777 /Users/james/unix1'], + 'host.risk.calculated_level': ['High'], + _id: ['e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c'], + 'process.hash.sha1': ['217490d4f51717aa3b301abec96be08602370d2d'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:37.869Z'], + }, + sort: [99, 1715086125028], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['643dddff1a57cbf70594854b44eb1a1d'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [73.02488], + 'rule.reference': [ + 'https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/prompt.py', + 'https://ss64.com/osx/osascript.html', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'bab17feba710b469e5d96820f0cb7ed511d983e5817f374ec3cb46462ac5b794', + ], + 'process.pid': [1206], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Software Signing'], + 'host.os.version': ['13.4'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': [ + 'Malicious Behavior Detection Alert: Potential Credentials Phishing via OSASCRIPT', + ], + 'host.name': ['SRVMAC08'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'group.name': ['staff'], + 'kibana.alert.workflow_status': ['open'], + 'rule.name': ['Potential Credentials Phishing via OSASCRIPT'], + 'threat.tactic.id': ['TA0006'], + 'threat.tactic.name': ['Credential Access'], + 'threat.technique.id': ['T1056'], + 'process.parent.args_count': [0], + 'threat.technique.subtechnique.reference': [ + 'https://attack.mitre.org/techniques/T1056/002/', + ], + 'process.name': ['osascript'], + 'threat.technique.subtechnique.name': ['GUI Input Capture'], + 'process.parent.code_signature.trusted': [false], + _id: ['2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f'], + 'threat.technique.name': ['Input Capture'], + 'group.id': ['20'], + 'threat.tactic.reference': ['https://attack.mitre.org/tactics/TA0006/'], + 'user.name': ['james'], + 'threat.framework': ['MITRE ATT&CK'], + 'process.code_signature.signing_id': ['com.apple.osascript'], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.executable': ['/usr/bin/osascript'], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.args': [ + 'osascript', + '-e', + 'display dialog "MacOS wants to access System Preferences\n\t\t\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬', + ], + 'process.code_signature.status': ['No error.'], + message: [ + 'Malicious Behavior Detection Alert: Potential Credentials Phishing via OSASCRIPT', + ], + '@timestamp': ['2024-05-07T12:48:45.027Z'], + 'threat.technique.subtechnique.id': ['T1056.002'], + 'threat.technique.reference': ['https://attack.mitre.org/techniques/T1056/'], + 'process.command_line': [ + 'osascript -e display dialog "MacOS wants to access System Preferences\n\t\t\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬', + ], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['0568baae15c752208ae56d8f9c737976d6de2e3a'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:09.909Z'], + }, + sort: [99, 1715086125027], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '2a9f7602de8656d30dda0ddcf79e78037ac2929780e13d5b2047b3bedc40bb69', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['/sbin/launchd'], + 'process.parent.name': ['launchd'], + 'user.name': ['root'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1200], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['No error.'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.491455], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': ['Software Signing'], + 'process.parent.executable': ['/sbin/launchd'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['My Go Application.app'], + 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': ['/sbin/launchd'], + '@timestamp': ['2024-05-07T12:48:45.023Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', + ], + 'host.risk.calculated_level': ['High'], + _id: ['2a9f7602de8656d30dda0ddcf79e78037ac2929780e13d5b2047b3bedc40bb69'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:06.888Z'], + }, + sort: [99, 1715086125023], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '4615c3a90e8057ae5cc9b358bbbf4298e346277a2f068dda052b0b43ef6d5bbd', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/3C4D44B9-4838-4613-BACC-BD00A9CE4025/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['/sbin/launchd'], + 'process.parent.name': ['launchd'], + 'user.name': ['root'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1169], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['No error.'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.491455], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/3C4D44B9-4838-4613-BACC-BD00A9CE4025/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': ['Software Signing'], + 'process.parent.executable': ['/sbin/launchd'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['My Go Application.app'], + 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': ['/sbin/launchd'], + '@timestamp': ['2024-05-07T12:48:45.022Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', + ], + 'host.risk.calculated_level': ['High'], + _id: ['4615c3a90e8057ae5cc9b358bbbf4298e346277a2f068dda052b0b43ef6d5bbd'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:27:47.362Z'], + }, + sort: [99, 1715086125022], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '449322a72d3f19efbdf983935a1bdd21ebd6b9c761ce31e8b252003017d7e5db', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/37D933EC-334D-410A-A741-0F730D6AE3FD/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['/sbin/launchd'], + 'process.parent.name': ['launchd'], + 'user.name': ['root'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1123], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['No error.'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.491455], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/37D933EC-334D-410A-A741-0F730D6AE3FD/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': ['Software Signing'], + 'process.parent.executable': ['/sbin/launchd'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['My Go Application.app'], + 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': ['/sbin/launchd'], + '@timestamp': ['2024-05-07T12:48:45.020Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', + ], + 'host.risk.calculated_level': ['High'], + _id: ['449322a72d3f19efbdf983935a1bdd21ebd6b9c761ce31e8b252003017d7e5db'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:25:24.716Z'], + }, + sort: [99, 1715086125020], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'f465ca9fbfc8bc3b1871e965c9e111cac76ff3f4076fed6bc9da88d49fb43014', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Shellcode Injection'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:45.017Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['f465ca9fbfc8bc3b1871e965c9e111cac76ff3f4076fed6bc9da88d49fb43014'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.051Z'], + }, + sort: [99, 1715086125017], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'aa283e6a13be77b533eceffb09e48254c8f91feeccc39f7eed80fd3881d053f4', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['C:\\Windows\\mpsvc.dll'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection', 'library'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['8dd620d9aeb35960bb766458c8890ede987c33d239cf730f93fe49d90ae759dd'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['mpsvc.dll'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:45.008Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['aa283e6a13be77b533eceffb09e48254c8f91feeccc39f7eed80fd3881d053f4'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:18.093Z'], + }, + sort: [99, 1715086125008], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'dd9e4ea23961ccfdb7a9c760ee6bedd19a013beac3b0d38227e7ae77ba4ce515', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['C:\\Windows\\mpsvc.dll'], + 'process.hash.md5': ['561cffbaba71a6e8cc1cdceda990ead4'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': ['C:\\Windows\\Explorer.EXE'], + 'process.parent.name': ['explorer.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', + ], + 'process.pid': [1008], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['8dd620d9aeb35960bb766458c8890ede987c33d239cf730f93fe49d90ae759dd'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\explorer.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['mpsvc.dll'], + 'process.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.code_signature.status': ['errorExpired'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.parent.args': ['C:\\Windows\\Explorer.EXE'], + '@timestamp': ['2024-05-07T12:48:45.007Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'host.risk.calculated_level': ['High'], + _id: ['dd9e4ea23961ccfdb7a9c760ee6bedd19a013beac3b0d38227e7ae77ba4ce515'], + 'process.hash.sha1': ['5162f14d75e96edb914d1756349d6e11583db0b0'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:17.887Z'], + }, + sort: [99, 1715086125007], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'f30d55e503b1d848b34ee57741b203d8052360dd873ea34802f3fa7a9ef34d0a', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.hash.md5': ['561cffbaba71a6e8cc1cdceda990ead4'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': ['C:\\Windows\\Explorer.EXE'], + 'process.parent.name': ['explorer.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', + ], + 'process.pid': [1008], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\explorer.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.code_signature.status': ['errorExpired'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.parent.args': ['C:\\Windows\\Explorer.EXE'], + '@timestamp': ['2024-05-07T12:48:45.006Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'host.risk.calculated_level': ['High'], + _id: ['f30d55e503b1d848b34ee57741b203d8052360dd873ea34802f3fa7a9ef34d0a'], + 'process.hash.sha1': ['5162f14d75e96edb914d1756349d6e11583db0b0'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:17.544Z'], + }, + sort: [99, 1715086125006], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '6f8cd5e8021dbb64598f2b7ec56bee21fd00d1e62d4e08905f86bf234873ee66', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.hash.md5': ['f070b5cf25febb9a88a168efd87c6112'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [''], + 'process.parent.name': ['userinit.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '567be4d1e15f4ff96d92e7d28e191076f5813f50be96bf4c3916e4ecf53f66cd', + ], + 'process.pid': [6228], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'process.pe.original_file_name': ['EXPLORER.EXE'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\explorer.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\System32\\userinit.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.args': ['C:\\Windows\\Explorer.EXE'], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.name': ['explorer.exe'], + '@timestamp': ['2024-05-07T12:48:45.004Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': ['C:\\Windows\\Explorer.EXE'], + 'host.risk.calculated_level': ['High'], + _id: ['6f8cd5e8021dbb64598f2b7ec56bee21fd00d1e62d4e08905f86bf234873ee66'], + 'process.hash.sha1': ['94518c310478e494082418ed295466f5aea26eea'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:37:18.152Z'], + }, + sort: [99, 1715086125004], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'ce110da958fe0cf0c07599a21c68d90a64c93b7607aa27970a614c7f49598316', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', + ], + 'process.hash.md5': ['f070b5cf25febb9a88a168efd87c6112'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [''], + 'process.parent.name': ['userinit.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '567be4d1e15f4ff96d92e7d28e191076f5813f50be96bf4c3916e4ecf53f66cd', + ], + 'process.pid': [6228], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'process.pe.original_file_name': ['EXPLORER.EXE'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\explorer.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\System32\\userinit.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'process.args': ['C:\\Windows\\Explorer.EXE'], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.name': ['explorer.exe'], + '@timestamp': ['2024-05-07T12:48:45.001Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': ['C:\\Windows\\Explorer.EXE'], + 'host.risk.calculated_level': ['High'], + _id: ['ce110da958fe0cf0c07599a21c68d90a64c93b7607aa27970a614c7f49598316'], + 'process.hash.sha1': ['94518c310478e494082418ed295466f5aea26eea'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:36:43.813Z'], + }, + sort: [99, 1715086125001], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '0866787b0027b4d908767ac16e35a1da00970c83632ba85be65f2ad371132b4f', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection', 'process', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Ransomware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'Ransomware.files.data': [ + '2D002D002D003D003D003D0020005700', + '2D002D002D003D003D003D0020005700', + '2D002D002D003D003D003D0020005700', + ], + 'process.code_signature.trusted': [true], + 'Ransomware.files.metrics': ['CANARY_ACTIVITY'], + 'kibana.alert.workflow_status': ['open'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'Ransomware.files.score': [0, 0, 0], + 'process.parent.code_signature.trusted': [false], + _id: ['0866787b0027b4d908767ac16e35a1da00970c83632ba85be65f2ad371132b4f'], + 'Ransomware.version': ['1.6.0'], + 'user.name': ['Administrator'], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'Ransomware.files.operation': ['creation', 'creation', 'creation'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.Ext.token.integrity_level_name': ['high'], + 'Ransomware.files.path': [ + 'c:\\hd3vuk19y-readme.txt', + 'c:\\$winreagent\\hd3vuk19y-readme.txt', + 'c:\\aaantiransomelastic-do-not-touch-dab6d40c-a6a1-442c-adc4-9d57a47e58d7\\hd3vuk19y-readme.txt', + ], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'Ransomware.files.entropy': [3.629971457026797, 3.629971457026797, 3.629971457026797], + 'Ransomware.feature': ['canary'], + 'Ransomware.files.extension': ['txt', 'txt', 'txt'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Ransomware Detection Alert'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:45.000Z'], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.964Z'], + }, + sort: [99, 1715086125000], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'b0fdf96721e361e1137d49a67e26d92f96b146392d7f44322bddc3d660abaef1', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Shellcode Injection'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:44.996Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['b0fdf96721e361e1137d49a67e26d92f96b146392d7f44322bddc3d660abaef1'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.174Z'], + }, + sort: [99, 1715086124996], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '7b4f49f21cf141e67856d3207fb4ea069c8035b41f0ea501970694cf8bd43cbe', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Shellcode Injection'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:44.986Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['7b4f49f21cf141e67856d3207fb4ea069c8035b41f0ea501970694cf8bd43cbe'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.066Z'], + }, + sort: [99, 1715086124986], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'ea81d79104cbd442236b5bcdb7a3331de897aa4ce1523e622068038d048d0a9e', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.Ext.memory_region.malware_signature.primary.matches': [ + 'WVmF9nQli1UIg2YEAIk+iwoLSgQ=', + 'dQxy0zPAQF9eW4vlXcMzwOv1VYvsgw==', + 'DIsEsIN4BAV1HP9wCP9wDP91DP8=', + '+4tF/FCLCP9RCF6Lx19bi+Vdw1U=', + 'vAAAADPSi030i/GLRfAPpMEBwe4f', + 'VIvO99GLwiNN3PfQM030I8czReiJ', + 'DIlGDIXAdSozwOtsi0YIhcB0Yms=', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': [ + 'Memory Threat Detection Alert: Windows.Ransomware.Sodinokibi', + ], + 'host.name': ['SRVWIN02'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'kibana.alert.workflow_status': ['open'], + 'rule.name': ['Windows.Ransomware.Sodinokibi'], + 'process.parent.args_count': [1], + 'process.Ext.memory_region.bytes_compressed_present': [false], + 'process.name': ['MsMpEng.exe'], + 'process.parent.code_signature.trusted': [false], + _id: ['ea81d79104cbd442236b5bcdb7a3331de897aa4ce1523e622068038d048d0a9e'], + 'user.name': ['Administrator'], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.Ext.memory_region.malware_signature.all_names': [ + 'Windows.Ransomware.Sodinokibi', + ], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.Ext.memory_region.malware_signature.primary.signature.name': [ + 'Windows.Ransomware.Sodinokibi', + ], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Windows.Ransomware.Sodinokibi'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:44.975Z'], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:25.169Z'], + }, + sort: [99, 1715086124975], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'cdf3b5510bb5ed622e8cefd1ce6bedc52bdd99a4c1ead537af0603469e713c8b', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll'], + 'process.hash.md5': ['4bfef0b578515c16b9582e32b78d2594'], + 'event.category': ['malware', 'intrusion_detection', 'library'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['C:\\Programdata\\Q3C7N1V8.exe'], + 'process.parent.name': ['Q3C7N1V8.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '70d21cbdc527559c4931421e66aa819b86d5af5535445ace467e74518164c46a', + ], + 'process.pid': [7824], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [false], + 'process.pe.original_file_name': ['RUNDLL32.EXE'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['12e6642cf6413bdf5388bee663080fa299591b2ba023d069286f3be9647547c8'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN01'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\SysWOW64\\rundll32.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.executable': ['C:\\ProgramData\\Q3C7N1V8.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['cdnver.dll'], + 'process.args': [ + 'C:\\Windows\\System32\\rundll32.exe', + 'C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll,#1', + ], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['rundll32.exe'], + 'process.parent.args': ['C:\\Programdata\\Q3C7N1V8.exe'], + '@timestamp': ['2024-05-07T12:47:32.838Z'], + 'process.command_line': [ + '"C:\\Windows\\System32\\rundll32.exe" "C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll",#1', + ], + 'host.risk.calculated_level': ['High'], + _id: ['cdf3b5510bb5ed622e8cefd1ce6bedc52bdd99a4c1ead537af0603469e713c8b'], + 'process.hash.sha1': ['9b16507aaf10a0aafa0df2ba83e8eb2708d83a02'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-16T01:51:26.472Z'], + }, + sort: [99, 1715086052838], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '6abe81eb6350fb08031761be029e7ab19f7e577a7c17a9c5ea1ed010ba1620e3', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['4bfef0b578515c16b9582e32b78d2594'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['C:\\Programdata\\Q3C7N1V8.exe'], + 'process.parent.name': ['Q3C7N1V8.exe'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '70d21cbdc527559c4931421e66aa819b86d5af5535445ace467e74518164c46a', + ], + 'process.pid': [7824], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': [ + 'Malicious Behavior Detection Alert: RunDLL32 with Unusual Arguments', + ], + 'host.name': ['SRVWIN01'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'kibana.alert.workflow_status': ['open'], + 'rule.name': ['RunDLL32 with Unusual Arguments'], + 'threat.tactic.id': ['TA0005'], + 'threat.tactic.name': ['Defense Evasion'], + 'threat.technique.id': ['T1218'], + 'process.parent.args_count': [1], + 'threat.technique.subtechnique.reference': [ + 'https://attack.mitre.org/techniques/T1218/011/', + ], + 'process.name': ['rundll32.exe'], + 'threat.technique.subtechnique.name': ['Rundll32'], + _id: ['6abe81eb6350fb08031761be029e7ab19f7e577a7c17a9c5ea1ed010ba1620e3'], + 'threat.technique.name': ['System Binary Proxy Execution'], + 'threat.tactic.reference': ['https://attack.mitre.org/tactics/TA0005/'], + 'user.name': ['Administrator'], + 'threat.framework': ['MITRE ATT&CK'], + 'process.working_directory': ['C:\\Users\\Administrator\\Documents\\'], + 'process.pe.original_file_name': ['RUNDLL32.EXE'], + 'event.module': ['endpoint'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\SysWOW64\\rundll32.exe'], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.executable': ['C:\\ProgramData\\Q3C7N1V8.exe'], + 'process.args': [ + 'C:\\Windows\\System32\\rundll32.exe', + 'C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll,#1', + ], + 'process.code_signature.status': ['trusted'], + message: ['Malicious Behavior Detection Alert: RunDLL32 with Unusual Arguments'], + 'process.parent.args': ['C:\\Programdata\\Q3C7N1V8.exe'], + '@timestamp': ['2024-05-07T12:47:32.836Z'], + 'threat.technique.subtechnique.id': ['T1218.011'], + 'threat.technique.reference': ['https://attack.mitre.org/techniques/T1218/'], + 'process.command_line': [ + '"C:\\Windows\\System32\\rundll32.exe" "C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll",#1', + ], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['9b16507aaf10a0aafa0df2ba83e8eb2708d83a02'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-16T01:51:26.348Z'], + }, + sort: [99, 1715086052836], + }, + ], + }, +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts new file mode 100644 index 0000000000000..a40dde44f8d67 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { GraphState } from '../../../../types'; + +export const discardPreviousGenerations = ({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected, + state, +}: { + generationAttempts: number; + hallucinationFailures: number; + isHallucinationDetected: boolean; + state: GraphState; +}): GraphState => { + return { + ...state, + combinedGenerations: '', // <-- reset the combined generations + generationAttempts: generationAttempts + 1, + generations: [], // <-- reset the generations + hallucinationFailures: isHallucinationDetected + ? hallucinationFailures + 1 + : hallucinationFailures, + }; +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts similarity index 70% rename from x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts index bc290bf172382..287f5e6b2130a 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts @@ -4,15 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getAttackDiscoveryPrompt } from './get_attack_discovery_prompt'; -describe('getAttackDiscoveryPrompt', () => { - it('should generate the correct attack discovery prompt', () => { +import { getAlertsContextPrompt } from '.'; +import { getDefaultAttackDiscoveryPrompt } from '../../../helpers/get_default_attack_discovery_prompt'; + +describe('getAlertsContextPrompt', () => { + it('generates the correct prompt', () => { const anonymizedAlerts = ['Alert 1', 'Alert 2', 'Alert 3']; - const expected = `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. + const expected = `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. You MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds). -Use context from the following open and acknowledged alerts to provide insights: +Use context from the following alerts to provide insights: """ Alert 1 @@ -23,7 +25,10 @@ Alert 3 """ `; - const prompt = getAttackDiscoveryPrompt({ anonymizedAlerts }); + const prompt = getAlertsContextPrompt({ + anonymizedAlerts, + attackDiscoveryPrompt: getDefaultAttackDiscoveryPrompt(), + }); expect(prompt).toEqual(expected); }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts new file mode 100644 index 0000000000000..d92d935053577 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/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. + */ + +// NOTE: we ask the LLM to `provide insights`. We do NOT use the feature name, `AttackDiscovery`, in the prompt. +export const getAlertsContextPrompt = ({ + anonymizedAlerts, + attackDiscoveryPrompt, +}: { + anonymizedAlerts: string[]; + attackDiscoveryPrompt: string; +}) => `${attackDiscoveryPrompt} + +Use context from the following alerts to provide insights: + +""" +${anonymizedAlerts.join('\n\n')} +""" +`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts new file mode 100644 index 0000000000000..fb7cf6bd59f98 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GraphState } from '../../../../types'; + +export const getAnonymizedAlertsFromState = (state: GraphState): string[] => + state.anonymizedAlerts.map((doc) => doc.pageContent); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts new file mode 100644 index 0000000000000..face2a6afc6bc --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { AttackDiscovery } from '@kbn/elastic-assistant-common'; + +import { getMaxRetriesReached } from '../../../../helpers/get_max_retries_reached'; + +export const getUseUnrefinedResults = ({ + generationAttempts, + maxGenerationAttempts, + unrefinedResults, +}: { + generationAttempts: number; + maxGenerationAttempts: number; + unrefinedResults: AttackDiscovery[] | null; +}): boolean => { + const nextAttemptWouldExcedLimit = getMaxRetriesReached({ + generationAttempts: generationAttempts + 1, // + 1, because we just used an attempt + maxGenerationAttempts, + }); + + return nextAttemptWouldExcedLimit && unrefinedResults != null && unrefinedResults.length > 0; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts new file mode 100644 index 0000000000000..1fcd81622f0fe --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { Logger } from '@kbn/core/server'; + +import { discardPreviousGenerations } from './helpers/discard_previous_generations'; +import { extractJson } from '../helpers/extract_json'; +import { getAnonymizedAlertsFromState } from './helpers/get_anonymized_alerts_from_state'; +import { getChainWithFormatInstructions } from '../helpers/get_chain_with_format_instructions'; +import { getCombined } from '../helpers/get_combined'; +import { getCombinedAttackDiscoveryPrompt } from '../helpers/get_combined_attack_discovery_prompt'; +import { generationsAreRepeating } from '../helpers/generations_are_repeating'; +import { getUseUnrefinedResults } from './helpers/get_use_unrefined_results'; +import { parseCombinedOrThrow } from '../helpers/parse_combined_or_throw'; +import { responseIsHallucinated } from '../helpers/response_is_hallucinated'; +import type { GraphState } from '../../types'; + +export const getGenerateNode = ({ + llm, + logger, +}: { + llm: ActionsClientLlm; + logger?: Logger; +}): ((state: GraphState) => Promise) => { + const generate = async (state: GraphState): Promise => { + logger?.debug(() => `---GENERATE---`); + + const anonymizedAlerts: string[] = getAnonymizedAlertsFromState(state); + + const { + attackDiscoveryPrompt, + combinedGenerations, + generationAttempts, + generations, + hallucinationFailures, + maxGenerationAttempts, + maxRepeatedGenerations, + } = state; + + let combinedResponse = ''; // mutable, because it must be accessed in the catch block + let partialResponse = ''; // mutable, because it must be accessed in the catch block + + try { + const query = getCombinedAttackDiscoveryPrompt({ + anonymizedAlerts, + attackDiscoveryPrompt, + combinedMaybePartialResults: combinedGenerations, + }); + + const { chain, formatInstructions, llmType } = getChainWithFormatInstructions(llm); + + logger?.debug( + () => `generate node is invoking the chain (${llmType}), attempt ${generationAttempts}` + ); + + const rawResponse = (await chain.invoke({ + format_instructions: formatInstructions, + query, + })) as unknown as string; + + // LOCAL MUTATION: + partialResponse = extractJson(rawResponse); // remove the surrounding ```json``` + + // if the response is hallucinated, discard previous generations and start over: + if (responseIsHallucinated(partialResponse)) { + logger?.debug( + () => + `generate node detected a hallucination (${llmType}), on attempt ${generationAttempts}; discarding the accumulated generations and starting over` + ); + + return discardPreviousGenerations({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: true, + state, + }); + } + + // if the generations are repeating, discard previous generations and start over: + if ( + generationsAreRepeating({ + currentGeneration: partialResponse, + previousGenerations: generations, + sampleLastNGenerations: maxRepeatedGenerations, + }) + ) { + logger?.debug( + () => + `generate node detected (${llmType}), detected ${maxRepeatedGenerations} repeated generations on attempt ${generationAttempts}; discarding the accumulated results and starting over` + ); + + // discard the accumulated results and start over: + return discardPreviousGenerations({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: false, + state, + }); + } + + // LOCAL MUTATION: + combinedResponse = getCombined({ combinedGenerations, partialResponse }); // combine the new response with the previous ones + + const unrefinedResults = parseCombinedOrThrow({ + combinedResponse, + generationAttempts, + llmType, + logger, + nodeName: 'generate', + }); + + // use the unrefined results if we already reached the max number of retries: + const useUnrefinedResults = getUseUnrefinedResults({ + generationAttempts, + maxGenerationAttempts, + unrefinedResults, + }); + + if (useUnrefinedResults) { + logger?.debug( + () => + `generate node is using unrefined results response (${llm._llmType()}) from attempt ${generationAttempts}, because all attempts have been used` + ); + } + + return { + ...state, + attackDiscoveries: useUnrefinedResults ? unrefinedResults : null, // optionally skip the refinement step by returning the final answer + combinedGenerations: combinedResponse, + generationAttempts: generationAttempts + 1, + generations: [...generations, partialResponse], + unrefinedResults, + }; + } catch (error) { + const parsingError = `generate node is unable to parse (${llm._llmType()}) response from attempt ${generationAttempts}; (this may be an incomplete response from the model): ${error}`; + logger?.debug(() => parsingError); // logged at debug level because the error is expected when the model returns an incomplete response + + return { + ...state, + combinedGenerations: combinedResponse, + errors: [...state.errors, parsingError], + generationAttempts: generationAttempts + 1, + generations: [...generations, partialResponse], + }; + } + }; + + return generate; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts new file mode 100644 index 0000000000000..05210799f151c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts @@ -0,0 +1,84 @@ +/* + * 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. + */ + +/* + * 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 { z } from '@kbn/zod'; + +export const SYNTAX = '{{ field.name fieldValue1 fieldValue2 fieldValueN }}'; +const GOOD_SYNTAX_EXAMPLES = + 'Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }}'; + +const BAD_SYNTAX_EXAMPLES = + 'Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}'; + +const RECONNAISSANCE = 'Reconnaissance'; +const INITIAL_ACCESS = 'Initial Access'; +const EXECUTION = 'Execution'; +const PERSISTENCE = 'Persistence'; +const PRIVILEGE_ESCALATION = 'Privilege Escalation'; +const DISCOVERY = 'Discovery'; +const LATERAL_MOVEMENT = 'Lateral Movement'; +const COMMAND_AND_CONTROL = 'Command and Control'; +const EXFILTRATION = 'Exfiltration'; + +const MITRE_ATTACK_TACTICS = [ + RECONNAISSANCE, + INITIAL_ACCESS, + EXECUTION, + PERSISTENCE, + PRIVILEGE_ESCALATION, + DISCOVERY, + LATERAL_MOVEMENT, + COMMAND_AND_CONTROL, + EXFILTRATION, +] as const; + +export const AttackDiscoveriesGenerationSchema = z.object({ + insights: z + .array( + z.object({ + alertIds: z.string().array().describe(`The alert IDs that the insight is based on.`), + detailsMarkdown: z + .string() + .describe( + `A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` + ), + entitySummaryMarkdown: z + .string() + .optional() + .describe( + `A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same ${SYNTAX} syntax` + ), + mitreAttackTactics: z + .string() + .array() + .optional() + .describe( + `An array of MITRE ATT&CK tactic for the insight, using one of the following values: ${MITRE_ATTACK_TACTICS.join( + ',' + )}` + ), + summaryMarkdown: z + .string() + .describe(`A markdown summary of insight, using the same ${SYNTAX} syntax`), + title: z + .string() + .describe( + 'A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.' + ), + }) + ) + .describe( + `Insights with markdown that always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` + ), +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts new file mode 100644 index 0000000000000..fd824709f5fcf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const addTrailingBackticksIfNecessary = (text: string): string => { + const leadingJSONpattern = /^\w*```json(.*?)/s; + const trailingBackticksPattern = /(.*?)```\w*$/s; + + const hasLeadingJSONWrapper = leadingJSONpattern.test(text); + const hasTrailingBackticks = trailingBackticksPattern.test(text); + + if (hasLeadingJSONWrapper && !hasTrailingBackticks) { + return `${text}\n\`\`\``; + } + + return text; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts new file mode 100644 index 0000000000000..5e13ec9f0dafe --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { extractJson } from '.'; + +describe('extractJson', () => { + it('returns the JSON text surrounded by ```json and ``` with no whitespace or additional text', () => { + const input = '```json{"key": "value"}```'; + + const expected = '{"key": "value"}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('returns the JSON block when surrounded by additional text and whitespace', () => { + const input = + 'You asked for some JSON, here it is:\n```json\n{"key": "value"}\n```\nI hope that works for you.'; + + const expected = '{"key": "value"}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('returns the original text if no JSON block is found', () => { + const input = "There's no JSON here, just some text."; + + expect(extractJson(input)).toBe(input); + }); + + it('trims leading and trailing whitespace from the extracted JSON', () => { + const input = 'Text before\n```json\n {"key": "value"} \n```\nText after'; + + const expected = '{"key": "value"}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('handles incomplete JSON blocks with no trailing ```', () => { + const input = 'Text before\n```json\n{"key": "value"'; // <-- no closing ```, because incomplete generation + + expect(extractJson(input)).toBe('{"key": "value"'); + }); + + it('handles multiline json (real world edge case)', () => { + const input = + '```json\n{\n "insights": [\n {\n "alertIds": [\n "a609473a23b3a66a40f2bba06795c28a0c12863c6931f39e472d069f5600cbae",\n "04a9ded2b4f10ea407711f0010d426ad328eea43ae53e1e0bf166c058947dff6",\n "8d53b9838181299b3c0b1544ea469216d72ad2234a1cce44017dd248a08d78d1",\n "51d0080ffcc1982dbae7c31a9a021f7b51422000dec1f0e0bb58bd61d934c893",\n "d93302956bee58d538f6f7a6cbf944e549e8466dacfb554a302dce46a069eef0",\n "75c89f679397f089716034cde20f5547a2e6bdd1606b1e002e0976ab339c4cd9",\n "5d8e9427c0ecc4daa5809bfe250b9a382c53e81e8f39eec87499d28efdda9300",\n "f18ac1874f510fd3fabb0ae48d0714f4952b294496ef1d993e3eb03f839e2d83",\n "e37cb31213c4c4e80beaf9f75e7966f88cdd86a228c6cb1a28e46356410fa78f",\n "cf70077b8888e8fbe434808fddbaf65d97fff244bb185a595cf0ad487e9c5850",\n "01bea609f0880b10b7b3c6cf6e8245ef0f134386fdcbf2a167e72487e0bda616",\n "289621edc88fd8b4775c541e46bcfdea40538291266179c59a5ca5afbee74cfc",\n "ba121c2045058b62a92e6a3abadd3c78a005b89129630e2271b2f45d5fd995b2",\n "fceb940b252be079df3629550d852bd2793f79071c917227268fa1b805abc8d1",\n "7044589c27bab148cdb97d9e2eeb88bd924fca82a6a05a53ec94dcadf8e56303",\n "1b68be35429f52280456aab17dd94191fe5c47fed9768f00d9f9e9044a08bbb5",\n "52478d4a119bbc44bec67f384f83dfa20b33cf9963177e619cd47a32bababe12",\n "fecbbb8924493b466e8f5744e0875a9ee91f326213b691b576b13da3fb875ebf",\n "c46bbdeb7b59f52c976e7e4f30e3d5c65f417d716cb140096b5edba52b1449a1",\n "f12caebcbda087fc8b49cdced64a8997dd1428f4cf91ebb251434a55126399b3",\n "c7478edbd13af443cfafc57d50e5206c5ae8c0f9c9cabc073fdc2d3946559617",\n "3585ae62651929ef405f9783410d7a94f4254d299205e22f22966178f189bb11",\n "f50f531912af1d31a66a0e37d4c0d9c571c2cca6bef2c7f8453eb3ab67c4d1a0",\n "95a9403f0bb97d03fc3c2eb06386503831766f541b736468088092c5e0e25830",\n "c1292c67f3ccd2cb2651c601f0816122cfa459276fa5fc89b40c62d1a793963e",\n "8911886e1b2964176f70eaee2aa6693ce101ee9c8ec5434acdc7ff18616ec31c",\n "bfbfb02c03c6f69fc2352c48d8fd7c7e4b557c611e16956fbb63e337a513e699",\n "064cbdc1932029fcb34f6ba685211b971afde3b8aa4325054bedaf4c9e4587ed",\n "9fd5d0ca9b9fff6e37f1114ad874103badb2b0570ef143cd4a26a553effdff00",\n "9e2687f26f04b5a8def3266f89fbe7217da2d4355c3b035268df1802f1342c81",\n "64557c4006c52119c01f6e3e582ce1b8207b2e8f64aaaa630ca1fd156c01ea1e",\n "df98d2568c986d101af055f78c7e2a39299627531c28012b5025d10e2ec1b208",\n "10683db11fb21cae36577f83722c686c2fc691d2be6fc4396f2733564f3210d1",\n "f46e7b3266200e3e23b15b5acea7bb934e2c17d23058e10daeed51f036f4932b",\n "3c77d55f912b80b66cc1e4e1df02a22ddee07c50338a409374fb2567d2fb4ca3",\n "8ec169c0fdf558c0d9d9ad8dedad0898b15bb718421b4cab8f5cce4ebcb78254",\n "4119a1705f993588f8d1d576e567ec17f102aeafe535e53bb56ec833418ccd08",\n "b53d06bfd23ab843dba67e2fde0da6364475b0bfb9c40cb8a6641cc4ecadec01",\n "1dcd85c8279fd7152dadecfc547cce06261d23ef4589fe4fdcc92b1ceeb76c0f",\n "d4ed490b1d39925ee612058655030bdb7cecda3e5893e1c40dbbac852b72fbc6",\n "2ecc96c4d51f5338684c08e7c67357e504abfec6fc4f21753a3c941189db68e1",\n "0c9fb123686bc739d117ee4f607ffbcef39f1f72e7eab6d01b70bbb40480b3d6",\n "162be5e04f54a5cd475d2437fe769ee044324b0a32ce83a735f61719b8b5fd63",\n "21eae60b4b29f7f01cc7006372374e1c5d6912858c33397cdbe4470df97fba79",\n "0409539590b6d9b80f7071d3d5658434f982ba7957aa6a5037f8b7a73b70100d",\n "5e8e654df34a9053f8b90e4ade25520dbee5994ebf7da531e1e7255d029ab031",\n "3ef381b2d29d71bc3ac8580d333344948a2664855a89ff037299a8b4aa663293",\n "0aef1fe2506842f9c53549049b47a8166bcc3d6efe2d8fcf1e57f3a634ed137c",\n "c2d12dacd0cd6ef4a7386c8d0146d3eb91a7e1e9f2d8d47bffaab07a92577993",\n "45e6663c65172e225e2531df3dce58096ed6e9a7d0fd7819e5b6f094a41731a0",\n "f2af064d46f1db1d96c7c9508a462993851e42f29566f2101ea3a1c51e5e451c",\n "b75c046d06f86eea41826999211ab5e6c9cb5fe067ade561fb5dc5f0b52d4584",\n "1fb9fbb26b78c2e9c56abf8e39e4cb278a5a382d53115dcb1624fdefca762865",\n "d78c4d12f6d50278be6320df1fe10beeef8723558cdb12d9d6c7d1aa8180498b",\n "c8fa7d3a31906893c47df234318e94bc4371b55ac54edc60b2c09afd8a9291c6",\n "5236dc9c55f19d8aed50078cc6ecd1de85041afa65003276fc311c14d5a74d0a",\n "efb9d548ff94246a22cfa8e06b70689d8f3edf69c8ad45c3811e0d340b4b10ff",\n "842c8d78d995f49b569934cf5e8316ba1d93a1d73e757210d5f0eb7e1ed52049",\n "b95dcfba35d31ab263bfab939280c71893bdb39e3a744c2f3cc38612ebcbb42a",\n "d6387171a203c64fd1c09514a028cf813d2ffccf968831c92cdf22287992e004",\n "b8d098f358ce5e8fa2900ac18435078652353a32a19ef2fd038bf82eee3a0731"\n ],\n "detailsMarkdown": "### Attack Progression\\n- **Initial Access**: The attack began with a spearphishing attachment delivered via Microsoft Office documents. The documents contained malicious macros that executed upon opening.\\n- **Execution**: The malicious macros executed various commands, including the use of `certutil` to decode and execute payloads, and `regsvr32` to register malicious DLLs.\\n- **Persistence**: The attackers established persistence by modifying registry run keys and creating scheduled tasks.\\n- **Credential Access**: The attackers attempted to capture credentials using `osascript` on macOS systems.\\n- **Defense Evasion**: The attackers used code signing with invalid or expired certificates to evade detection.\\n- **Command and Control**: The attackers established command and control channels using various techniques, including the use of `mshta` and `powershell` scripts.\\n- **Exfiltration**: The attackers exfiltrated data using tools like `curl` to transfer data to remote servers.\\n- **Impact**: The attackers deployed ransomware, including `Sodinokibi` and `Bumblebee`, to encrypt files and demand ransom payments.\\n\\n### Affected Hosts and Users\\n- **Hosts**: Multiple hosts across different operating systems (Windows, macOS, Linux) were affected.\\n- **Users**: The attacks targeted various users, including administrators and regular users.\\n\\n### Known Threat Groups\\n- The attack patterns and techniques used in this campaign are consistent with those employed by known threat groups such as `Emotet`, `Qbot`, and `Sodinokibi`.\\n\\n### Recommendations\\n- **Immediate Actions**: Isolate affected systems, reset passwords, and review network traffic for signs of command and control communications.\\n- **Long-term Actions**: Implement multi-factor authentication, conduct regular security awareness training, and deploy advanced endpoint protection solutions.",\n "entitySummaryMarkdown": "{{ host.name 9ed6a9db-da4d-4877-a2b4-f7a22cc55e9a }} {{ user.name c45d8d76-bff6-4c4b-aa5a-62eb15d68adb }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence",\n "Credential Access",\n "Defense Evasion",\n "Command and Control",\n "Exfiltration",\n "Impact"\n ],\n "summaryMarkdown": "A sophisticated multi-stage attack was detected, involving spearphishing, credential access, and ransomware deployment. The attack targeted multiple hosts and users across different operating systems.",\n "title": "Multi-Stage Cyber Attack Detected"\n }\n ]\n}\n```'; + + const expected = + '{\n "insights": [\n {\n "alertIds": [\n "a609473a23b3a66a40f2bba06795c28a0c12863c6931f39e472d069f5600cbae",\n "04a9ded2b4f10ea407711f0010d426ad328eea43ae53e1e0bf166c058947dff6",\n "8d53b9838181299b3c0b1544ea469216d72ad2234a1cce44017dd248a08d78d1",\n "51d0080ffcc1982dbae7c31a9a021f7b51422000dec1f0e0bb58bd61d934c893",\n "d93302956bee58d538f6f7a6cbf944e549e8466dacfb554a302dce46a069eef0",\n "75c89f679397f089716034cde20f5547a2e6bdd1606b1e002e0976ab339c4cd9",\n "5d8e9427c0ecc4daa5809bfe250b9a382c53e81e8f39eec87499d28efdda9300",\n "f18ac1874f510fd3fabb0ae48d0714f4952b294496ef1d993e3eb03f839e2d83",\n "e37cb31213c4c4e80beaf9f75e7966f88cdd86a228c6cb1a28e46356410fa78f",\n "cf70077b8888e8fbe434808fddbaf65d97fff244bb185a595cf0ad487e9c5850",\n "01bea609f0880b10b7b3c6cf6e8245ef0f134386fdcbf2a167e72487e0bda616",\n "289621edc88fd8b4775c541e46bcfdea40538291266179c59a5ca5afbee74cfc",\n "ba121c2045058b62a92e6a3abadd3c78a005b89129630e2271b2f45d5fd995b2",\n "fceb940b252be079df3629550d852bd2793f79071c917227268fa1b805abc8d1",\n "7044589c27bab148cdb97d9e2eeb88bd924fca82a6a05a53ec94dcadf8e56303",\n "1b68be35429f52280456aab17dd94191fe5c47fed9768f00d9f9e9044a08bbb5",\n "52478d4a119bbc44bec67f384f83dfa20b33cf9963177e619cd47a32bababe12",\n "fecbbb8924493b466e8f5744e0875a9ee91f326213b691b576b13da3fb875ebf",\n "c46bbdeb7b59f52c976e7e4f30e3d5c65f417d716cb140096b5edba52b1449a1",\n "f12caebcbda087fc8b49cdced64a8997dd1428f4cf91ebb251434a55126399b3",\n "c7478edbd13af443cfafc57d50e5206c5ae8c0f9c9cabc073fdc2d3946559617",\n "3585ae62651929ef405f9783410d7a94f4254d299205e22f22966178f189bb11",\n "f50f531912af1d31a66a0e37d4c0d9c571c2cca6bef2c7f8453eb3ab67c4d1a0",\n "95a9403f0bb97d03fc3c2eb06386503831766f541b736468088092c5e0e25830",\n "c1292c67f3ccd2cb2651c601f0816122cfa459276fa5fc89b40c62d1a793963e",\n "8911886e1b2964176f70eaee2aa6693ce101ee9c8ec5434acdc7ff18616ec31c",\n "bfbfb02c03c6f69fc2352c48d8fd7c7e4b557c611e16956fbb63e337a513e699",\n "064cbdc1932029fcb34f6ba685211b971afde3b8aa4325054bedaf4c9e4587ed",\n "9fd5d0ca9b9fff6e37f1114ad874103badb2b0570ef143cd4a26a553effdff00",\n "9e2687f26f04b5a8def3266f89fbe7217da2d4355c3b035268df1802f1342c81",\n "64557c4006c52119c01f6e3e582ce1b8207b2e8f64aaaa630ca1fd156c01ea1e",\n "df98d2568c986d101af055f78c7e2a39299627531c28012b5025d10e2ec1b208",\n "10683db11fb21cae36577f83722c686c2fc691d2be6fc4396f2733564f3210d1",\n "f46e7b3266200e3e23b15b5acea7bb934e2c17d23058e10daeed51f036f4932b",\n "3c77d55f912b80b66cc1e4e1df02a22ddee07c50338a409374fb2567d2fb4ca3",\n "8ec169c0fdf558c0d9d9ad8dedad0898b15bb718421b4cab8f5cce4ebcb78254",\n "4119a1705f993588f8d1d576e567ec17f102aeafe535e53bb56ec833418ccd08",\n "b53d06bfd23ab843dba67e2fde0da6364475b0bfb9c40cb8a6641cc4ecadec01",\n "1dcd85c8279fd7152dadecfc547cce06261d23ef4589fe4fdcc92b1ceeb76c0f",\n "d4ed490b1d39925ee612058655030bdb7cecda3e5893e1c40dbbac852b72fbc6",\n "2ecc96c4d51f5338684c08e7c67357e504abfec6fc4f21753a3c941189db68e1",\n "0c9fb123686bc739d117ee4f607ffbcef39f1f72e7eab6d01b70bbb40480b3d6",\n "162be5e04f54a5cd475d2437fe769ee044324b0a32ce83a735f61719b8b5fd63",\n "21eae60b4b29f7f01cc7006372374e1c5d6912858c33397cdbe4470df97fba79",\n "0409539590b6d9b80f7071d3d5658434f982ba7957aa6a5037f8b7a73b70100d",\n "5e8e654df34a9053f8b90e4ade25520dbee5994ebf7da531e1e7255d029ab031",\n "3ef381b2d29d71bc3ac8580d333344948a2664855a89ff037299a8b4aa663293",\n "0aef1fe2506842f9c53549049b47a8166bcc3d6efe2d8fcf1e57f3a634ed137c",\n "c2d12dacd0cd6ef4a7386c8d0146d3eb91a7e1e9f2d8d47bffaab07a92577993",\n "45e6663c65172e225e2531df3dce58096ed6e9a7d0fd7819e5b6f094a41731a0",\n "f2af064d46f1db1d96c7c9508a462993851e42f29566f2101ea3a1c51e5e451c",\n "b75c046d06f86eea41826999211ab5e6c9cb5fe067ade561fb5dc5f0b52d4584",\n "1fb9fbb26b78c2e9c56abf8e39e4cb278a5a382d53115dcb1624fdefca762865",\n "d78c4d12f6d50278be6320df1fe10beeef8723558cdb12d9d6c7d1aa8180498b",\n "c8fa7d3a31906893c47df234318e94bc4371b55ac54edc60b2c09afd8a9291c6",\n "5236dc9c55f19d8aed50078cc6ecd1de85041afa65003276fc311c14d5a74d0a",\n "efb9d548ff94246a22cfa8e06b70689d8f3edf69c8ad45c3811e0d340b4b10ff",\n "842c8d78d995f49b569934cf5e8316ba1d93a1d73e757210d5f0eb7e1ed52049",\n "b95dcfba35d31ab263bfab939280c71893bdb39e3a744c2f3cc38612ebcbb42a",\n "d6387171a203c64fd1c09514a028cf813d2ffccf968831c92cdf22287992e004",\n "b8d098f358ce5e8fa2900ac18435078652353a32a19ef2fd038bf82eee3a0731"\n ],\n "detailsMarkdown": "### Attack Progression\\n- **Initial Access**: The attack began with a spearphishing attachment delivered via Microsoft Office documents. The documents contained malicious macros that executed upon opening.\\n- **Execution**: The malicious macros executed various commands, including the use of `certutil` to decode and execute payloads, and `regsvr32` to register malicious DLLs.\\n- **Persistence**: The attackers established persistence by modifying registry run keys and creating scheduled tasks.\\n- **Credential Access**: The attackers attempted to capture credentials using `osascript` on macOS systems.\\n- **Defense Evasion**: The attackers used code signing with invalid or expired certificates to evade detection.\\n- **Command and Control**: The attackers established command and control channels using various techniques, including the use of `mshta` and `powershell` scripts.\\n- **Exfiltration**: The attackers exfiltrated data using tools like `curl` to transfer data to remote servers.\\n- **Impact**: The attackers deployed ransomware, including `Sodinokibi` and `Bumblebee`, to encrypt files and demand ransom payments.\\n\\n### Affected Hosts and Users\\n- **Hosts**: Multiple hosts across different operating systems (Windows, macOS, Linux) were affected.\\n- **Users**: The attacks targeted various users, including administrators and regular users.\\n\\n### Known Threat Groups\\n- The attack patterns and techniques used in this campaign are consistent with those employed by known threat groups such as `Emotet`, `Qbot`, and `Sodinokibi`.\\n\\n### Recommendations\\n- **Immediate Actions**: Isolate affected systems, reset passwords, and review network traffic for signs of command and control communications.\\n- **Long-term Actions**: Implement multi-factor authentication, conduct regular security awareness training, and deploy advanced endpoint protection solutions.",\n "entitySummaryMarkdown": "{{ host.name 9ed6a9db-da4d-4877-a2b4-f7a22cc55e9a }} {{ user.name c45d8d76-bff6-4c4b-aa5a-62eb15d68adb }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence",\n "Credential Access",\n "Defense Evasion",\n "Command and Control",\n "Exfiltration",\n "Impact"\n ],\n "summaryMarkdown": "A sophisticated multi-stage attack was detected, involving spearphishing, credential access, and ransomware deployment. The attack targeted multiple hosts and users across different operating systems.",\n "title": "Multi-Stage Cyber Attack Detected"\n }\n ]\n}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('handles "Here is my analysis of the security events in JSON format" (real world edge case)', () => { + const input = + 'Here is my analysis of the security events in JSON format:\n\n```json\n{\n "insights": [\n {\n "alertIds": [\n "d776c8406fd81427b1f166550ac1b949017da7a13dc734594e4b05f24622b26e",\n "504c012054cfe91986311b4e6bc8523914434fab590e5c07c0328fab6566753c",\n "b706b8c19e68cc4f54b69f0a93e32b10f4102b610213b7826fb1d303b90a0536",\n "7763ebe716c47f64987362a9fb120d73873c77d26ad915f2c3d57c5dd3b7eed0",\n "25c61e0423a9bfd7f268ca6e9b67d4f507207c0cb1e1b4701aa5248cb3866f1f",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:17.566Z }}, a malicious file with SHA256 hash {{ file.hash.sha256 74ef6cc38f5a1a80148752b63c117e6846984debd2af806c65887195a8eccc56 }} was detected on {{ host.name SRVNIX05 }}\\n- The file was initially downloaded as a zip archive and extracted to /home/ubuntu/\\n- The malware, identified as Linux.Trojan.BPFDoor, was then copied to /dev/shm/kdmtmpflush and executed\\n- This trojan allows remote attackers to gain backdoor access to the compromised Linux system\\n- The malware was executed with root privileges, indicating a serious compromise\\n- Network connections and other malicious activities from this backdoor should be investigated",\n "entitySummaryMarkdown": "{{ host.name SRVNIX05 }} compromised by Linux.Trojan.BPFDoor malware executed as {{ user.name root }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Linux.Trojan.BPFDoor malware detected and executed on {{ host.name SRVNIX05 }} with root privileges, allowing remote backdoor access",\n "title": "Linux Trojan BPFDoor Backdoor Detected"\n },\n {\n "alertIds": [\n "5946b409f49b0983de53e575db0874ef11b0544766f816dc702941a69a9b0dd1",\n "aa0ba23872c48a8ee761591c5bb0a9ed8258c51b27111cc72dbe8624a0b7da96",\n "b60a5c344b579cab9406becdec14a11d56f4eccc2bf6caaf6eb72ddf1707124c",\n "4920ca19a22968e4ab0cf299974234699d9cce15545c401a2b8fd09d71f6e106",\n "26302b2afbe58c8dcfde950c7164262c626af0b85f0808f3d8632b1d6a406d16",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "41564c953dd101b942537110d175d2b269959c24dbf5b7c482e32851ab6f5dc1",\n "12e102970920f5f938b21effb09394c00540075fc4057ec79e221046a8b6ba0f"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:33.570Z }}, suspicious activity was detected on {{ host.name SRVMAC08 }}\\n- A malicious application \\"My Go Application.app\\" was executed, likely masquerading as a legitimate program\\n- The malware attempted to access the user\'s keychain to steal credentials\\n- It executed a file named {{ file.name unix1 }} which tried to access {{ file.path /Users/james/library/Keychains/login.keychain-db }}\\n- The malware also attempted to display a fake system preferences dialog to phish the user\'s password\\n- This attack targeted {{ user.name james }}, who has high user criticality",\n "entitySummaryMarkdown": "{{ host.name SRVMAC08 }} infected with malware targeting {{ user.name james }}\'s credentials",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Credential Access" \n ],\n "summaryMarkdown": "Malware on {{ host.name SRVMAC08 }} attempted to steal keychain credentials and phish password from {{ user.name james }}",\n "title": "macOS Credential Theft Attempt"\n },\n {\n "alertIds": [\n "a492cd3202717d0c86f9b44623b12ac4d19855722e0fadb2f84a547afb45871a",\n "7fdf3a399b0a6df74784f478c2712a0e47ff997f73701593b3a5a56fa452056f",\n "bf33e5f004b6f6f41e362f929b3fa16b5ea9ecbb0f6389acd17dfcfb67ff3ae9",\n "b6559664247c438f9cd15022feb87855253c3cef882cc52d2e064f2693977f1c",\n "636a5a24b810bf2dbc5e2417858ac218b1fadb598fa55676745f88c0509f3e48",\n "fc0f6f9939277cc4f526148c15813f5d48094e557fdcf0ba9e773b2a16ec8c2e",\n "0029a93e8f72dce05a22ca0cc5a5cd1ca8a29b93b3c8864f7623f10b98d79084",\n "67f41b973f82fc141d75fbbd1d6caba11066c19b2a1c720fcec9e681e1cfa60c",\n "79774ae772225e94b6183f5ea394572ebe24452be99100bab145173c57c73d3b"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:54.836Z }}, malicious activity was detected on {{ host.name SRVWIN01 }}\\n- An Excel file was used to drop and execute malware\\n- The malware used certutil.exe to decode a malicious payload\\n- A suspicious executable {{ file.name Q3C7N1V8.exe }} was created in C:\\\\ProgramData\\\\\\n- The malware established persistence by modifying registry run keys\\n- It then executed a DLL {{ file.name cdnver.dll }} using rundll32.exe\\n- This attack chain indicates a sophisticated malware infection, likely part of an ongoing attack campaign",\n "entitySummaryMarkdown": "{{ host.name SRVWIN01 }} infected via malicious Excel file executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access", \n "Execution",\n "Persistence",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN01 }} via malicious Excel file, establishing persistence and executing additional payloads",\n "title": "Excel-based Malware Infection Chain"\n },\n {\n "alertIds": [\n "801ec41afa5f05a7cafefe4eaff87be1f9eb7ecbfcfc501bd83a12f19e742be0",\n "eafd7577e1d88b2c4fc3d0e3eb54b2a315f79996f075ba3c57d6f2ae7181c53b",\n "eb8fee0ceacc8caec4757e95ec132a42bae4ba7841126ce9616873e01e806ddf",\n "69dcd5e48424cc8a04a965f5bec7539c8221ac556a7b93c531cdc7e02b58c191",\n "6c81da91ad4ec313c5a4aa970e1fdf7c3ee6dbfa8536c734bd12c72f1abe3a09",\n "584d904ea196623eb794df40565797656e24d05a707638447b5e53c05d520510",\n "46d05beb516dae1ad2f168084cdeb5bfd35ac1b1194bd65aa1c837fb3b77c21d",\n "c79fe367d985d9a5d9ee723ce94977b88fe1bbb3ec8e2ffbb7b3ee134d6b49ef",\n "3ef6baa7c7c99cad5b7832e6a778a7d1ea2d88729a3e50fbf2b821d0e57f2740",\n "1fbe36af64b587d7604812f6a248754cfe8c1d80b0551046c1fc95640d0ba538",\n "4451f6a45edc2d90f85717925071457e88dd41d0ee3d3c377f5721a254651513",\n "7ec9f53a2c4571325476ad2f4de3d2ecb49609b35a4a30a33d8d57e815d09f52",\n "ca57fd3a83e06419ce8299eefd3c783bd3d33b46ce47ffd27e2abdcb2b3e0955"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:14.847Z }}, a malicious OneNote file was opened on {{ host.name SRVWIN04 }}\\n- The OneNote file executed an embedded HTA file using mshta.exe\\n- The HTA file then downloaded additional malware using curl.exe\\n- A suspicious DLL {{ file.path C:\\\\ProgramData\\\\121.png }} was loaded using rundll32.exe\\n- The malware injected shellcode into legitimate Windows processes like AtBroker.exe\\n- Memory scans detected signatures matching the Qbot banking trojan\\n- The malware established persistence by modifying registry run keys\\n- It also performed domain trust enumeration, indicating potential lateral movement preparation\\n- This sophisticated attack chain suggests a targeted intrusion by an advanced threat actor",\n "entitySummaryMarkdown": "{{ host.name SRVWIN04 }} compromised via malicious OneNote file opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution", \n "Persistence",\n "Defense Evasion",\n "Discovery"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN04 }} via OneNote file, downloading Qbot trojan and preparing for potential lateral movement",\n "title": "OneNote-based Qbot Infection Chain"\n },\n {\n "alertIds": [\n "7150ee5a9571c6028573bf7d9c2ed0da15c3387ee3c8f668741799496f7b4ae9",\n "6053ca3481a9307d3a8626fe055357541bb53d97f5deb1b7b346ec86441c335b",\n "d9c3908a4ac46b90270e6aab8217ab6385a114574931026f1df8cfc930260ff6",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070",\n "f045dc2a57582944b6e198e685e98bf02f86b5eb23ddbbdbb015c8568867122c",\n "171fe0490d48e9cac6f5b46aec7bfa67f3ecb96af308027018ca881bae1ce5d7",\n "0e22ea9514fd663a3841a212b19736fd1579c301d80f4838f25adeec24de4cf6",\n "9d8fdb59213e5a950d93253f9f986c730c877a70493c4f47ad0de52ef50c42f1"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:58.609Z }}, a malicious executable was run on {{ host.name SRVWIN02 }}\\n- The malware injected shellcode into the legitimate MsMpEng.exe (Windows Defender) process\\n- Memory scans detected signatures matching the Sodinokibi (REvil) ransomware\\n- The malware created ransom notes and began encrypting files\\n- It also attempted to enable network discovery, likely to spread to other systems\\n- This indicates an active ransomware infection that could quickly spread across the network",\n "entitySummaryMarkdown": "{{ host.name SRVWIN02 }} infected with Sodinokibi ransomware executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion",\n "Impact"\n ],\n "summaryMarkdown": "Sodinokibi (REvil) ransomware detected on {{ host.name SRVWIN02 }}, actively encrypting files and attempting to spread",\n "title": "Active Sodinokibi Ransomware Infection"\n },\n {\n "alertIds": [\n "6f8e71d59956c6dbed5c88986cdafd4386684e3879085b2742e1f2d38b282066",\n "c13b78fbfef05ddc81c73b436ccb5288d8cd52a46175638b1b3b0d311f8b53e8",\n "b0f3d3f5bfc0b1d1f3c7e219ee44dc225fa26cafd40697073a636b44cf6054ad"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:22.077Z }}, suspicious activity was detected on {{ host.name SRVWIN06 }}\\n- The msiexec.exe process spawned an unusual PowerShell child process\\n- The PowerShell process executed a script from a suspicious temporary directory\\n- Memory scans of the PowerShell process detected signatures matching the Bumblebee malware loader\\n- Bumblebee is known to be used by multiple ransomware groups as an initial access vector\\n- This indicates a likely ongoing attack attempting to deploy additional malware or ransomware",\n "entitySummaryMarkdown": "{{ host.name SRVWIN06 }} infected with Bumblebee malware loader via {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Bumblebee malware loader detected on {{ host.name SRVWIN06 }}, likely attempting to deploy additional payloads",\n "title": "Bumblebee Malware Loader Detected"\n },\n {\n "alertIds": [\n "f629babc51c3628517d8a7e1f0662124ee41e4328b1dbcf72dc3fc6f2e410d33",\n "627d00600f803366edb83700b546a4bf486e2990ac7140d842e898eb6e298e83",\n "6181847506974ed4458f03b60919c4a306197b5cb040ab324d2d1f6d0ca5bde1",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "df26b2d23068b77fdc001ea44f46505a259f02ceccc9fa0b2401c5e35190e710",\n "9c038ff779bd0ff514a1ff2b55caa359189d8bcebc48c6ac14a789946e87eaed"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:27.839Z }}, a malicious Word document was opened on {{ host.name SRVWIN07 }}\\n- The document spawned wscript.exe to execute a malicious VBS script\\n- The VBS script then launched a PowerShell process with suspicious arguments\\n- PowerShell was used to create a scheduled task for persistence\\n- This attack chain indicates a likely attempt to establish a foothold for further malicious activities",\n "entitySummaryMarkdown": "{{ host.name SRVWIN07 }} compromised via malicious Word document opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Malicious Word document on {{ host.name SRVWIN07 }} led to execution of VBS and PowerShell scripts, establishing persistence via scheduled task",\n "title": "Malicious Document Leads to Persistence"\n }\n ]\n}'; + + const expected = + '{\n "insights": [\n {\n "alertIds": [\n "d776c8406fd81427b1f166550ac1b949017da7a13dc734594e4b05f24622b26e",\n "504c012054cfe91986311b4e6bc8523914434fab590e5c07c0328fab6566753c",\n "b706b8c19e68cc4f54b69f0a93e32b10f4102b610213b7826fb1d303b90a0536",\n "7763ebe716c47f64987362a9fb120d73873c77d26ad915f2c3d57c5dd3b7eed0",\n "25c61e0423a9bfd7f268ca6e9b67d4f507207c0cb1e1b4701aa5248cb3866f1f",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:17.566Z }}, a malicious file with SHA256 hash {{ file.hash.sha256 74ef6cc38f5a1a80148752b63c117e6846984debd2af806c65887195a8eccc56 }} was detected on {{ host.name SRVNIX05 }}\\n- The file was initially downloaded as a zip archive and extracted to /home/ubuntu/\\n- The malware, identified as Linux.Trojan.BPFDoor, was then copied to /dev/shm/kdmtmpflush and executed\\n- This trojan allows remote attackers to gain backdoor access to the compromised Linux system\\n- The malware was executed with root privileges, indicating a serious compromise\\n- Network connections and other malicious activities from this backdoor should be investigated",\n "entitySummaryMarkdown": "{{ host.name SRVNIX05 }} compromised by Linux.Trojan.BPFDoor malware executed as {{ user.name root }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Linux.Trojan.BPFDoor malware detected and executed on {{ host.name SRVNIX05 }} with root privileges, allowing remote backdoor access",\n "title": "Linux Trojan BPFDoor Backdoor Detected"\n },\n {\n "alertIds": [\n "5946b409f49b0983de53e575db0874ef11b0544766f816dc702941a69a9b0dd1",\n "aa0ba23872c48a8ee761591c5bb0a9ed8258c51b27111cc72dbe8624a0b7da96",\n "b60a5c344b579cab9406becdec14a11d56f4eccc2bf6caaf6eb72ddf1707124c",\n "4920ca19a22968e4ab0cf299974234699d9cce15545c401a2b8fd09d71f6e106",\n "26302b2afbe58c8dcfde950c7164262c626af0b85f0808f3d8632b1d6a406d16",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "41564c953dd101b942537110d175d2b269959c24dbf5b7c482e32851ab6f5dc1",\n "12e102970920f5f938b21effb09394c00540075fc4057ec79e221046a8b6ba0f"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:33.570Z }}, suspicious activity was detected on {{ host.name SRVMAC08 }}\\n- A malicious application \\"My Go Application.app\\" was executed, likely masquerading as a legitimate program\\n- The malware attempted to access the user\'s keychain to steal credentials\\n- It executed a file named {{ file.name unix1 }} which tried to access {{ file.path /Users/james/library/Keychains/login.keychain-db }}\\n- The malware also attempted to display a fake system preferences dialog to phish the user\'s password\\n- This attack targeted {{ user.name james }}, who has high user criticality",\n "entitySummaryMarkdown": "{{ host.name SRVMAC08 }} infected with malware targeting {{ user.name james }}\'s credentials",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Credential Access" \n ],\n "summaryMarkdown": "Malware on {{ host.name SRVMAC08 }} attempted to steal keychain credentials and phish password from {{ user.name james }}",\n "title": "macOS Credential Theft Attempt"\n },\n {\n "alertIds": [\n "a492cd3202717d0c86f9b44623b12ac4d19855722e0fadb2f84a547afb45871a",\n "7fdf3a399b0a6df74784f478c2712a0e47ff997f73701593b3a5a56fa452056f",\n "bf33e5f004b6f6f41e362f929b3fa16b5ea9ecbb0f6389acd17dfcfb67ff3ae9",\n "b6559664247c438f9cd15022feb87855253c3cef882cc52d2e064f2693977f1c",\n "636a5a24b810bf2dbc5e2417858ac218b1fadb598fa55676745f88c0509f3e48",\n "fc0f6f9939277cc4f526148c15813f5d48094e557fdcf0ba9e773b2a16ec8c2e",\n "0029a93e8f72dce05a22ca0cc5a5cd1ca8a29b93b3c8864f7623f10b98d79084",\n "67f41b973f82fc141d75fbbd1d6caba11066c19b2a1c720fcec9e681e1cfa60c",\n "79774ae772225e94b6183f5ea394572ebe24452be99100bab145173c57c73d3b"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:54.836Z }}, malicious activity was detected on {{ host.name SRVWIN01 }}\\n- An Excel file was used to drop and execute malware\\n- The malware used certutil.exe to decode a malicious payload\\n- A suspicious executable {{ file.name Q3C7N1V8.exe }} was created in C:\\\\ProgramData\\\\\\n- The malware established persistence by modifying registry run keys\\n- It then executed a DLL {{ file.name cdnver.dll }} using rundll32.exe\\n- This attack chain indicates a sophisticated malware infection, likely part of an ongoing attack campaign",\n "entitySummaryMarkdown": "{{ host.name SRVWIN01 }} infected via malicious Excel file executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access", \n "Execution",\n "Persistence",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN01 }} via malicious Excel file, establishing persistence and executing additional payloads",\n "title": "Excel-based Malware Infection Chain"\n },\n {\n "alertIds": [\n "801ec41afa5f05a7cafefe4eaff87be1f9eb7ecbfcfc501bd83a12f19e742be0",\n "eafd7577e1d88b2c4fc3d0e3eb54b2a315f79996f075ba3c57d6f2ae7181c53b",\n "eb8fee0ceacc8caec4757e95ec132a42bae4ba7841126ce9616873e01e806ddf",\n "69dcd5e48424cc8a04a965f5bec7539c8221ac556a7b93c531cdc7e02b58c191",\n "6c81da91ad4ec313c5a4aa970e1fdf7c3ee6dbfa8536c734bd12c72f1abe3a09",\n "584d904ea196623eb794df40565797656e24d05a707638447b5e53c05d520510",\n "46d05beb516dae1ad2f168084cdeb5bfd35ac1b1194bd65aa1c837fb3b77c21d",\n "c79fe367d985d9a5d9ee723ce94977b88fe1bbb3ec8e2ffbb7b3ee134d6b49ef",\n "3ef6baa7c7c99cad5b7832e6a778a7d1ea2d88729a3e50fbf2b821d0e57f2740",\n "1fbe36af64b587d7604812f6a248754cfe8c1d80b0551046c1fc95640d0ba538",\n "4451f6a45edc2d90f85717925071457e88dd41d0ee3d3c377f5721a254651513",\n "7ec9f53a2c4571325476ad2f4de3d2ecb49609b35a4a30a33d8d57e815d09f52",\n "ca57fd3a83e06419ce8299eefd3c783bd3d33b46ce47ffd27e2abdcb2b3e0955"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:14.847Z }}, a malicious OneNote file was opened on {{ host.name SRVWIN04 }}\\n- The OneNote file executed an embedded HTA file using mshta.exe\\n- The HTA file then downloaded additional malware using curl.exe\\n- A suspicious DLL {{ file.path C:\\\\ProgramData\\\\121.png }} was loaded using rundll32.exe\\n- The malware injected shellcode into legitimate Windows processes like AtBroker.exe\\n- Memory scans detected signatures matching the Qbot banking trojan\\n- The malware established persistence by modifying registry run keys\\n- It also performed domain trust enumeration, indicating potential lateral movement preparation\\n- This sophisticated attack chain suggests a targeted intrusion by an advanced threat actor",\n "entitySummaryMarkdown": "{{ host.name SRVWIN04 }} compromised via malicious OneNote file opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution", \n "Persistence",\n "Defense Evasion",\n "Discovery"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN04 }} via OneNote file, downloading Qbot trojan and preparing for potential lateral movement",\n "title": "OneNote-based Qbot Infection Chain"\n },\n {\n "alertIds": [\n "7150ee5a9571c6028573bf7d9c2ed0da15c3387ee3c8f668741799496f7b4ae9",\n "6053ca3481a9307d3a8626fe055357541bb53d97f5deb1b7b346ec86441c335b",\n "d9c3908a4ac46b90270e6aab8217ab6385a114574931026f1df8cfc930260ff6",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070",\n "f045dc2a57582944b6e198e685e98bf02f86b5eb23ddbbdbb015c8568867122c",\n "171fe0490d48e9cac6f5b46aec7bfa67f3ecb96af308027018ca881bae1ce5d7",\n "0e22ea9514fd663a3841a212b19736fd1579c301d80f4838f25adeec24de4cf6",\n "9d8fdb59213e5a950d93253f9f986c730c877a70493c4f47ad0de52ef50c42f1"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:58.609Z }}, a malicious executable was run on {{ host.name SRVWIN02 }}\\n- The malware injected shellcode into the legitimate MsMpEng.exe (Windows Defender) process\\n- Memory scans detected signatures matching the Sodinokibi (REvil) ransomware\\n- The malware created ransom notes and began encrypting files\\n- It also attempted to enable network discovery, likely to spread to other systems\\n- This indicates an active ransomware infection that could quickly spread across the network",\n "entitySummaryMarkdown": "{{ host.name SRVWIN02 }} infected with Sodinokibi ransomware executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion",\n "Impact"\n ],\n "summaryMarkdown": "Sodinokibi (REvil) ransomware detected on {{ host.name SRVWIN02 }}, actively encrypting files and attempting to spread",\n "title": "Active Sodinokibi Ransomware Infection"\n },\n {\n "alertIds": [\n "6f8e71d59956c6dbed5c88986cdafd4386684e3879085b2742e1f2d38b282066",\n "c13b78fbfef05ddc81c73b436ccb5288d8cd52a46175638b1b3b0d311f8b53e8",\n "b0f3d3f5bfc0b1d1f3c7e219ee44dc225fa26cafd40697073a636b44cf6054ad"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:22.077Z }}, suspicious activity was detected on {{ host.name SRVWIN06 }}\\n- The msiexec.exe process spawned an unusual PowerShell child process\\n- The PowerShell process executed a script from a suspicious temporary directory\\n- Memory scans of the PowerShell process detected signatures matching the Bumblebee malware loader\\n- Bumblebee is known to be used by multiple ransomware groups as an initial access vector\\n- This indicates a likely ongoing attack attempting to deploy additional malware or ransomware",\n "entitySummaryMarkdown": "{{ host.name SRVWIN06 }} infected with Bumblebee malware loader via {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Bumblebee malware loader detected on {{ host.name SRVWIN06 }}, likely attempting to deploy additional payloads",\n "title": "Bumblebee Malware Loader Detected"\n },\n {\n "alertIds": [\n "f629babc51c3628517d8a7e1f0662124ee41e4328b1dbcf72dc3fc6f2e410d33",\n "627d00600f803366edb83700b546a4bf486e2990ac7140d842e898eb6e298e83",\n "6181847506974ed4458f03b60919c4a306197b5cb040ab324d2d1f6d0ca5bde1",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "df26b2d23068b77fdc001ea44f46505a259f02ceccc9fa0b2401c5e35190e710",\n "9c038ff779bd0ff514a1ff2b55caa359189d8bcebc48c6ac14a789946e87eaed"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:27.839Z }}, a malicious Word document was opened on {{ host.name SRVWIN07 }}\\n- The document spawned wscript.exe to execute a malicious VBS script\\n- The VBS script then launched a PowerShell process with suspicious arguments\\n- PowerShell was used to create a scheduled task for persistence\\n- This attack chain indicates a likely attempt to establish a foothold for further malicious activities",\n "entitySummaryMarkdown": "{{ host.name SRVWIN07 }} compromised via malicious Word document opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Malicious Word document on {{ host.name SRVWIN07 }} led to execution of VBS and PowerShell scripts, establishing persistence via scheduled task",\n "title": "Malicious Document Leads to Persistence"\n }\n ]\n}'; + + expect(extractJson(input)).toBe(expected); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts new file mode 100644 index 0000000000000..79d3f9c0d0599 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const extractJson = (input: string): string => { + const regex = /```json\s*([\s\S]*?)(?:\s*```|$)/; + const match = input.match(regex); + + if (match && match[1]) { + return match[1].trim(); + } + + return input; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx new file mode 100644 index 0000000000000..7d6db4dd72dfd --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx @@ -0,0 +1,90 @@ +/* + * 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 { generationsAreRepeating } from '.'; + +describe('getIsGenerationRepeating', () => { + it('returns true when all previous generations are the same as the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen1', 'gen1'], // <-- all the same, length 3 + sampleLastNGenerations: 3, + }); + + expect(result).toBe(true); + }); + + it('returns false when some of the previous generations are NOT the same as the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen2', 'gen1'], // <-- some are different, length 3 + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns true when all *sampled* generations are the same as the current generation, and there are older samples past the last N', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: [ + 'gen2', // <-- older sample will be ignored + 'gen1', + 'gen1', + 'gen1', + ], + sampleLastNGenerations: 3, + }); + + expect(result).toBe(true); + }); + + it('returns false when some of the *sampled* generations are NOT the same as the current generation, and there are additional samples past the last N', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: [ + 'gen1', // <-- older sample will be ignored + 'gen1', + 'gen1', + 'gen2', + ], + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns false when sampling fewer generations than sampleLastNGenerations, and all are the same as the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen1'], // <-- same, but only 2 generations + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns false when sampling fewer generations than sampleLastNGenerations, and some are different from the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen2'], // <-- different, but only 2 generations + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns false when there are no previous generations to sample', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: [], + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx new file mode 100644 index 0000000000000..6cc9cd86c9d2f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** Returns true if the last n generations are repeating the same output */ +export const generationsAreRepeating = ({ + currentGeneration, + previousGenerations, + sampleLastNGenerations, +}: { + currentGeneration: string; + previousGenerations: string[]; + sampleLastNGenerations: number; +}): boolean => { + const generationsToSample = previousGenerations.slice(-sampleLastNGenerations); + + if (generationsToSample.length < sampleLastNGenerations) { + return false; // Not enough generations to sample + } + + return generationsToSample.every((generation) => generation === currentGeneration); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts new file mode 100644 index 0000000000000..7eacaad1d7e39 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { Runnable } from '@langchain/core/runnables'; + +import { getOutputParser } from '../get_output_parser'; + +interface GetChainWithFormatInstructions { + chain: Runnable; + formatInstructions: string; + llmType: string; +} + +export const getChainWithFormatInstructions = ( + llm: ActionsClientLlm +): GetChainWithFormatInstructions => { + const outputParser = getOutputParser(); + const formatInstructions = outputParser.getFormatInstructions(); + + const prompt = ChatPromptTemplate.fromTemplate( + `Answer the user's question as best you can:\n{format_instructions}\n{query}` + ); + + const chain = prompt.pipe(llm); + const llmType = llm._llmType(); + + return { chain, formatInstructions, llmType }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts new file mode 100644 index 0000000000000..10b5c323891a1 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getCombined = ({ + combinedGenerations, + partialResponse, +}: { + combinedGenerations: string; + partialResponse: string; +}): string => `${combinedGenerations}${partialResponse}`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts new file mode 100644 index 0000000000000..4c9ac71f8310c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash/fp'; + +import { getAlertsContextPrompt } from '../../generate/helpers/get_alerts_context_prompt'; +import { getContinuePrompt } from '../get_continue_prompt'; + +/** + * Returns the the initial query, or the initial query combined with a + * continuation prompt and partial results + */ +export const getCombinedAttackDiscoveryPrompt = ({ + anonymizedAlerts, + attackDiscoveryPrompt, + combinedMaybePartialResults, +}: { + anonymizedAlerts: string[]; + attackDiscoveryPrompt: string; + /** combined results that may contain incomplete JSON */ + combinedMaybePartialResults: string; +}): string => { + const alertsContextPrompt = getAlertsContextPrompt({ + anonymizedAlerts, + attackDiscoveryPrompt, + }); + + return isEmpty(combinedMaybePartialResults) + ? alertsContextPrompt // no partial results yet + : `${alertsContextPrompt} + +${getContinuePrompt()} + +""" +${combinedMaybePartialResults} +""" + +`; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts new file mode 100644 index 0000000000000..628ba0531332c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getContinuePrompt = + (): string => `Continue exactly where you left off in the JSON output below, generating only the additional JSON output when it's required to complete your work. The additional JSON output MUST ALWAYS follow these rules: +1) it MUST conform to the schema above, because it will be checked against the JSON schema +2) it MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds), because it will be parsed as JSON +3) it MUST NOT repeat any the previous output, because that would prevent partial results from being combined +4) it MUST NOT restart from the beginning, because that would prevent partial results from being combined +5) it MUST NOT be prefixed or suffixed with additional text outside of the JSON, because that would prevent it from being combined and parsed as JSON: +`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts new file mode 100644 index 0000000000000..25bace13d40c8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultAttackDiscoveryPrompt = (): string => + "You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. You MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds)."; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts new file mode 100644 index 0000000000000..569c8cf4e04a5 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { getOutputParser } from '.'; + +describe('getOutputParser', () => { + it('returns a structured output parser with the expected format instructions', () => { + const outputParser = getOutputParser(); + + const expected = `You must format your output as a JSON value that adheres to a given \"JSON Schema\" instance. + +\"JSON Schema\" is a declarative language that allows you to annotate and validate JSON documents. + +For example, the example \"JSON Schema\" instance {{\"properties\": {{\"foo\": {{\"description\": \"a list of test words\", \"type\": \"array\", \"items\": {{\"type\": \"string\"}}}}}}, \"required\": [\"foo\"]}}}} +would match an object with one required property, \"foo\". The \"type\" property specifies \"foo\" must be an \"array\", and the \"description\" property semantically describes it as \"a list of test words\". The items within \"foo\" must be strings. +Thus, the object {{\"foo\": [\"bar\", \"baz\"]}} is a well-formatted instance of this example \"JSON Schema\". The object {{\"properties\": {{\"foo\": [\"bar\", \"baz\"]}}}} is not well-formatted. + +Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas! + +Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock: +\`\`\`json +{"type":"object","properties":{"insights":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"alertIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The alert IDs that the insight is based on.\"},\"detailsMarkdown\":{\"type\":\"string\",\"description\":\"A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"},\"entitySummaryMarkdown\":{\"type\":\"string\",\"description\":\"A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"mitreAttackTactics\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"An array of MITRE ATT&CK tactic for the insight, using one of the following values: Reconnaissance,Initial Access,Execution,Persistence,Privilege Escalation,Discovery,Lateral Movement,Command and Control,Exfiltration\"},\"summaryMarkdown\":{\"type\":\"string\",\"description\":\"A markdown summary of insight, using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"title\":{\"type\":\"string\",\"description\":\"A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.\"}},\"required\":[\"alertIds\",\"detailsMarkdown\",\"summaryMarkdown\",\"title\"],\"additionalProperties\":false},\"description\":\"Insights with markdown that always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"}},\"required\":[\"insights\"],\"additionalProperties":false,\"$schema\":\"http://json-schema.org/draft-07/schema#\"} +\`\`\` +`; + + expect(outputParser.getFormatInstructions()).toEqual(expected); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts new file mode 100644 index 0000000000000..2ca0d72b63eb4 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { StructuredOutputParser } from 'langchain/output_parsers'; + +import { AttackDiscoveriesGenerationSchema } from '../../generate/schema'; + +export const getOutputParser = () => + StructuredOutputParser.fromZodSchema(AttackDiscoveriesGenerationSchema); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts new file mode 100644 index 0000000000000..3f7a0a9d802b3 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { AttackDiscovery } from '@kbn/elastic-assistant-common'; + +import { addTrailingBackticksIfNecessary } from '../add_trailing_backticks_if_necessary'; +import { extractJson } from '../extract_json'; +import { AttackDiscoveriesGenerationSchema } from '../../generate/schema'; + +export const parseCombinedOrThrow = ({ + combinedResponse, + generationAttempts, + llmType, + logger, + nodeName, +}: { + /** combined responses that maybe valid JSON */ + combinedResponse: string; + generationAttempts: number; + nodeName: string; + llmType: string; + logger?: Logger; +}): AttackDiscovery[] => { + const timestamp = new Date().toISOString(); + + const extractedJson = extractJson(addTrailingBackticksIfNecessary(combinedResponse)); + + logger?.debug( + () => + `${nodeName} node is parsing extractedJson (${llmType}) from attempt ${generationAttempts}` + ); + + const unvalidatedParsed = JSON.parse(extractedJson); + + logger?.debug( + () => + `${nodeName} node is validating combined response (${llmType}) from attempt ${generationAttempts}` + ); + + const validatedResponse = AttackDiscoveriesGenerationSchema.parse(unvalidatedParsed); + + logger?.debug( + () => + `${nodeName} node successfully validated Attack discoveries response (${llmType}) from attempt ${generationAttempts}` + ); + + return [...validatedResponse.insights.map((insight) => ({ ...insight, timestamp }))]; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts new file mode 100644 index 0000000000000..f938f6436db98 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const responseIsHallucinated = (result: string): boolean => + result.includes('{{ host.name hostNameValue }}'); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts new file mode 100644 index 0000000000000..e642e598e73f0 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { GraphState } from '../../../../types'; + +export const discardPreviousRefinements = ({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected, + state, +}: { + generationAttempts: number; + hallucinationFailures: number; + isHallucinationDetected: boolean; + state: GraphState; +}): GraphState => { + return { + ...state, + combinedRefinements: '', // <-- reset the combined refinements + generationAttempts: generationAttempts + 1, + refinements: [], // <-- reset the refinements + hallucinationFailures: isHallucinationDetected + ? hallucinationFailures + 1 + : hallucinationFailures, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts new file mode 100644 index 0000000000000..11ea40a48ae55 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AttackDiscovery } from '@kbn/elastic-assistant-common'; +import { isEmpty } from 'lodash/fp'; + +import { getContinuePrompt } from '../../../helpers/get_continue_prompt'; + +/** + * Returns a prompt that combines the initial query, a refine prompt, and partial results + */ +export const getCombinedRefinePrompt = ({ + attackDiscoveryPrompt, + combinedRefinements, + refinePrompt, + unrefinedResults, +}: { + attackDiscoveryPrompt: string; + combinedRefinements: string; + refinePrompt: string; + unrefinedResults: AttackDiscovery[] | null; +}): string => { + const baseQuery = `${attackDiscoveryPrompt} + +${refinePrompt} + +""" +${JSON.stringify(unrefinedResults, null, 2)} +""" + +`; + + return isEmpty(combinedRefinements) + ? baseQuery // no partial results yet + : `${baseQuery} + +${getContinuePrompt()} + +""" +${combinedRefinements} +""" + +`; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts new file mode 100644 index 0000000000000..5743316669785 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultRefinePrompt = + (): string => `You previously generated the following insights, but sometimes they represent the same attack. + +Combine the insights below, when they represent the same attack; leave any insights that are not combined unchanged:`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts new file mode 100644 index 0000000000000..13d0a2228a3ee --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Note: the conditions tested here are different than the generate node + */ +export const getUseUnrefinedResults = ({ + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): boolean => maxRetriesReached || maxHallucinationFailuresReached; // we may have reached max halucination failures, but we still want to use the unrefined results diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts new file mode 100644 index 0000000000000..0c7987eef92bc --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { Logger } from '@kbn/core/server'; + +import { discardPreviousRefinements } from './helpers/discard_previous_refinements'; +import { extractJson } from '../helpers/extract_json'; +import { getChainWithFormatInstructions } from '../helpers/get_chain_with_format_instructions'; +import { getCombined } from '../helpers/get_combined'; +import { getCombinedRefinePrompt } from './helpers/get_combined_refine_prompt'; +import { generationsAreRepeating } from '../helpers/generations_are_repeating'; +import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; +import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; +import { getUseUnrefinedResults } from './helpers/get_use_unrefined_results'; +import { parseCombinedOrThrow } from '../helpers/parse_combined_or_throw'; +import { responseIsHallucinated } from '../helpers/response_is_hallucinated'; +import type { GraphState } from '../../types'; + +export const getRefineNode = ({ + llm, + logger, +}: { + llm: ActionsClientLlm; + logger?: Logger; +}): ((state: GraphState) => Promise) => { + const refine = async (state: GraphState): Promise => { + logger?.debug(() => '---REFINE---'); + + const { + attackDiscoveryPrompt, + combinedRefinements, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + maxRepeatedGenerations, + refinements, + refinePrompt, + unrefinedResults, + } = state; + + let combinedResponse = ''; // mutable, because it must be accessed in the catch block + let partialResponse = ''; // mutable, because it must be accessed in the catch block + + try { + const query = getCombinedRefinePrompt({ + attackDiscoveryPrompt, + combinedRefinements, + refinePrompt, + unrefinedResults, + }); + + const { chain, formatInstructions, llmType } = getChainWithFormatInstructions(llm); + + logger?.debug( + () => `refine node is invoking the chain (${llmType}), attempt ${generationAttempts}` + ); + + const rawResponse = (await chain.invoke({ + format_instructions: formatInstructions, + query, + })) as unknown as string; + + // LOCAL MUTATION: + partialResponse = extractJson(rawResponse); // remove the surrounding ```json``` + + // if the response is hallucinated, discard it: + if (responseIsHallucinated(partialResponse)) { + logger?.debug( + () => + `refine node detected a hallucination (${llmType}), on attempt ${generationAttempts}; discarding the accumulated refinements and starting over` + ); + + return discardPreviousRefinements({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: true, + state, + }); + } + + // if the refinements are repeating, discard previous refinements and start over: + if ( + generationsAreRepeating({ + currentGeneration: partialResponse, + previousGenerations: refinements, + sampleLastNGenerations: maxRepeatedGenerations, + }) + ) { + logger?.debug( + () => + `refine node detected (${llmType}), detected ${maxRepeatedGenerations} repeated generations on attempt ${generationAttempts}; discarding the accumulated results and starting over` + ); + + // discard the accumulated results and start over: + return discardPreviousRefinements({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: false, + state, + }); + } + + // LOCAL MUTATION: + combinedResponse = getCombined({ combinedGenerations: combinedRefinements, partialResponse }); // combine the new response with the previous ones + + const attackDiscoveries = parseCombinedOrThrow({ + combinedResponse, + generationAttempts, + llmType, + logger, + nodeName: 'refine', + }); + + return { + ...state, + attackDiscoveries, // the final, refined answer + generationAttempts: generationAttempts + 1, + combinedRefinements: combinedResponse, + refinements: [...refinements, partialResponse], + }; + } catch (error) { + const parsingError = `refine node is unable to parse (${llm._llmType()}) response from attempt ${generationAttempts}; (this may be an incomplete response from the model): ${error}`; + logger?.debug(() => parsingError); // logged at debug level because the error is expected when the model returns an incomplete response + + const maxRetriesReached = getMaxRetriesReached({ + generationAttempts: generationAttempts + 1, + maxGenerationAttempts, + }); + + const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ + hallucinationFailures, + maxHallucinationFailures, + }); + + // we will use the unrefined results if we have reached the maximum number of retries or hallucination failures: + const useUnrefinedResults = getUseUnrefinedResults({ + maxHallucinationFailuresReached, + maxRetriesReached, + }); + + if (useUnrefinedResults) { + logger?.debug( + () => + `refine node is using unrefined results response (${llm._llmType()}) from attempt ${generationAttempts}, because all attempts have been used` + ); + } + + return { + ...state, + attackDiscoveries: useUnrefinedResults ? unrefinedResults : null, + combinedRefinements: combinedResponse, + errors: [...state.errors, parsingError], + generationAttempts: generationAttempts + 1, + refinements: [...refinements, partialResponse], + }; + } + }; + + return refine; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts new file mode 100644 index 0000000000000..3a8b7ed3a6b94 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core/server'; +import { Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { CallbackManagerForRetrieverRun } from '@langchain/core/callbacks/manager'; +import type { Document } from '@langchain/core/documents'; +import { BaseRetriever, type BaseRetrieverInput } from '@langchain/core/retrievers'; + +import { getAnonymizedAlerts } from '../helpers/get_anonymized_alerts'; + +export type CustomRetrieverInput = BaseRetrieverInput; + +export class AnonymizedAlertsRetriever extends BaseRetriever { + lc_namespace = ['langchain', 'retrievers']; + + #alertsIndexPattern?: string; + #anonymizationFields?: AnonymizationFieldResponse[]; + #esClient: ElasticsearchClient; + #onNewReplacements?: (newReplacements: Replacements) => void; + #replacements?: Replacements; + #size?: number; + + constructor({ + alertsIndexPattern, + anonymizationFields, + fields, + esClient, + onNewReplacements, + replacements, + size, + }: { + alertsIndexPattern?: string; + anonymizationFields?: AnonymizationFieldResponse[]; + fields?: CustomRetrieverInput; + esClient: ElasticsearchClient; + onNewReplacements?: (newReplacements: Replacements) => void; + replacements?: Replacements; + size?: number; + }) { + super(fields); + + this.#alertsIndexPattern = alertsIndexPattern; + this.#anonymizationFields = anonymizationFields; + this.#esClient = esClient; + this.#onNewReplacements = onNewReplacements; + this.#replacements = replacements; + this.#size = size; + } + + async _getRelevantDocuments( + query: string, + runManager?: CallbackManagerForRetrieverRun + ): Promise { + const anonymizedAlerts = await getAnonymizedAlerts({ + alertsIndexPattern: this.#alertsIndexPattern, + anonymizationFields: this.#anonymizationFields, + esClient: this.#esClient, + onNewReplacements: this.#onNewReplacements, + replacements: this.#replacements, + size: this.#size, + }); + + return anonymizedAlerts.map((alert) => ({ + pageContent: alert, + metadata: {}, + })); + } +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts similarity index 90% rename from x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts index 6b7526870eb9f..b616c392ddd21 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts @@ -6,19 +6,19 @@ */ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { getOpenAndAcknowledgedAlertsQuery } from '@kbn/elastic-assistant-common'; -import { getAnonymizedAlerts } from './get_anonymized_alerts'; -import { mockOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_open_and_acknowledged_alerts_query_results'; -import { getOpenAndAcknowledgedAlertsQuery } from '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query'; -import { MIN_SIZE } from '../open_and_acknowledged_alerts/helpers'; +const MIN_SIZE = 10; -jest.mock('../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query', () => { - const original = jest.requireActual( - '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query' - ); +import { getAnonymizedAlerts } from '.'; +import { mockOpenAndAcknowledgedAlertsQueryResults } from '../../../../mock/mock_open_and_acknowledged_alerts_query_results'; + +jest.mock('@kbn/elastic-assistant-common', () => { + const original = jest.requireActual('@kbn/elastic-assistant-common'); return { - getOpenAndAcknowledgedAlertsQuery: jest.fn(() => original), + ...original, + getOpenAndAcknowledgedAlertsQuery: jest.fn(), }; }); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts similarity index 77% rename from x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts index 5989caf439518..bc2a7f5bf9e71 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts @@ -7,12 +7,16 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { ElasticsearchClient } from '@kbn/core/server'; -import type { Replacements } from '@kbn/elastic-assistant-common'; -import { getAnonymizedValue, transformRawData } from '@kbn/elastic-assistant-common'; -import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import { + Replacements, + getAnonymizedValue, + getOpenAndAcknowledgedAlertsQuery, + getRawDataOrDefault, + sizeIsOutOfRange, + transformRawData, +} from '@kbn/elastic-assistant-common'; -import { getOpenAndAcknowledgedAlertsQuery } from '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query'; -import { getRawDataOrDefault, sizeIsOutOfRange } from '../open_and_acknowledged_alerts/helpers'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; export const getAnonymizedAlerts = async ({ alertsIndexPattern, diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts new file mode 100644 index 0000000000000..951ae3bca8854 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; + +import { AnonymizedAlertsRetriever } from './anonymized_alerts_retriever'; +import type { GraphState } from '../../types'; + +export const getRetrieveAnonymizedAlertsNode = ({ + alertsIndexPattern, + anonymizationFields, + esClient, + logger, + onNewReplacements, + replacements, + size, +}: { + alertsIndexPattern?: string; + anonymizationFields?: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + logger?: Logger; + onNewReplacements?: (replacements: Replacements) => void; + replacements?: Replacements; + size?: number; +}): ((state: GraphState) => Promise) => { + let localReplacements = { ...(replacements ?? {}) }; + const localOnNewReplacements = (newReplacements: Replacements) => { + localReplacements = { ...localReplacements, ...newReplacements }; + + onNewReplacements?.(localReplacements); // invoke the callback with the latest replacements + }; + + const retriever = new AnonymizedAlertsRetriever({ + alertsIndexPattern, + anonymizationFields, + esClient, + onNewReplacements: localOnNewReplacements, + replacements, + size, + }); + + const retrieveAnonymizedAlerts = async (state: GraphState): Promise => { + logger?.debug(() => '---RETRIEVE ANONYMIZED ALERTS---'); + const documents = await retriever + .withConfig({ runName: 'runAnonymizedAlertsRetriever' }) + .invoke(''); + + return { + ...state, + anonymizedAlerts: documents, + replacements: localReplacements, + }; + }; + + return retrieveAnonymizedAlerts; +}; + +/** + * Retrieve documents + * + * @param {GraphState} state The current state of the graph. + * @param {RunnableConfig | undefined} config The configuration object for tracing. + * @returns {Promise} The new state object. + */ diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts new file mode 100644 index 0000000000000..4229155cc2e25 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import type { Document } from '@langchain/core/documents'; +import type { StateGraphArgs } from '@langchain/langgraph'; + +import { + DEFAULT_MAX_GENERATION_ATTEMPTS, + DEFAULT_MAX_HALLUCINATION_FAILURES, + DEFAULT_MAX_REPEATED_GENERATIONS, +} from '../constants'; +import { getDefaultAttackDiscoveryPrompt } from '../nodes/helpers/get_default_attack_discovery_prompt'; +import { getDefaultRefinePrompt } from '../nodes/refine/helpers/get_default_refine_prompt'; +import type { GraphState } from '../types'; + +export const getDefaultGraphState = (): StateGraphArgs['channels'] => ({ + attackDiscoveries: { + value: (x: AttackDiscovery[] | null, y?: AttackDiscovery[] | null) => y ?? x, + default: () => null, + }, + attackDiscoveryPrompt: { + value: (x: string, y?: string) => y ?? x, + default: () => getDefaultAttackDiscoveryPrompt(), + }, + anonymizedAlerts: { + value: (x: Document[], y?: Document[]) => y ?? x, + default: () => [], + }, + combinedGenerations: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + combinedRefinements: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + errors: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + generationAttempts: { + value: (x: number, y?: number) => y ?? x, + default: () => 0, + }, + generations: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + hallucinationFailures: { + value: (x: number, y?: number) => y ?? x, + default: () => 0, + }, + refinePrompt: { + value: (x: string, y?: string) => y ?? x, + default: () => getDefaultRefinePrompt(), + }, + maxGenerationAttempts: { + value: (x: number, y?: number) => y ?? x, + default: () => DEFAULT_MAX_GENERATION_ATTEMPTS, + }, + maxHallucinationFailures: { + value: (x: number, y?: number) => y ?? x, + default: () => DEFAULT_MAX_HALLUCINATION_FAILURES, + }, + maxRepeatedGenerations: { + value: (x: number, y?: number) => y ?? x, + default: () => DEFAULT_MAX_REPEATED_GENERATIONS, + }, + refinements: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + replacements: { + value: (x: Replacements, y?: Replacements) => y ?? x, + default: () => ({}), + }, + unrefinedResults: { + value: (x: AttackDiscovery[] | null, y?: AttackDiscovery[] | null) => y ?? x, + default: () => null, + }, +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts new file mode 100644 index 0000000000000..b4473a02b82ae --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import type { Document } from '@langchain/core/documents'; + +export interface GraphState { + attackDiscoveries: AttackDiscovery[] | null; + attackDiscoveryPrompt: string; + anonymizedAlerts: Document[]; + combinedGenerations: string; + combinedRefinements: string; + errors: string[]; + generationAttempts: number; + generations: string[]; + hallucinationFailures: number; + maxGenerationAttempts: number; + maxHallucinationFailures: number; + maxRepeatedGenerations: number; + refinements: string[]; + refinePrompt: string; + replacements: Replacements; + unrefinedResults: AttackDiscovery[] | null; +} diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts similarity index 94% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts index 6e9cc39597bd7..a82ec24c7041e 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts @@ -10,11 +10,11 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { createAttackDiscovery } from './create_attack_discovery'; import { AttackDiscoveryCreateProps, AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { getAttackDiscovery } from './get_attack_discovery'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; import { loggerMock } from '@kbn/logging-mocks'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); -jest.mock('./get_attack_discovery'); +jest.mock('../get_attack_discovery/get_attack_discovery'); const attackDiscoveryCreate: AttackDiscoveryCreateProps = { attackDiscoveries: [], apiConfig: { diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts index 7304ab3488529..fc511dc559d30 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts @@ -9,8 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryCreateProps, AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { getAttackDiscovery } from './get_attack_discovery'; -import { CreateAttackDiscoverySchema } from './types'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; +import { CreateAttackDiscoverySchema } from '../types'; export interface CreateAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/field_maps_configuration.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration.ts similarity index 100% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/field_maps_configuration.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration.ts diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts index e80d1e4589838..945603b517938 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts @@ -8,8 +8,8 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { EsAttackDiscoverySchema } from './types'; -import { transformESSearchToAttackDiscovery } from './transforms'; +import { EsAttackDiscoverySchema } from '../types'; +import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; const MAX_ITEMS = 10000; export interface FindAllAttackDiscoveriesParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts index 10688ce25b25e..53d74e6e92f42 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts index 532c35ac89c05..07fde44080026 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts @@ -7,8 +7,8 @@ import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from './types'; -import { transformESSearchToAttackDiscovery } from './transforms'; +import { EsAttackDiscoverySchema } from '../types'; +import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; export interface FindAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts index 4ee89fb7a3bc0..af1a1827cbddd 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts @@ -8,7 +8,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { getAttackDiscovery } from './get_attack_discovery'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; import { AuthenticatedUser } from '@kbn/core-security-common'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts index d0cf6fd19ae05..ae2051d9e480b 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts @@ -7,8 +7,8 @@ import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from './types'; -import { transformESSearchToAttackDiscovery } from './transforms'; +import { EsAttackDiscoverySchema } from '../types'; +import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; export interface GetAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts index ca053743c8035..5aac100f5f52c 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts @@ -11,12 +11,15 @@ import { AttackDiscoveryResponse, } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { findAllAttackDiscoveries } from './find_all_attack_discoveries'; -import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id'; -import { updateAttackDiscovery } from './update_attack_discovery'; -import { createAttackDiscovery } from './create_attack_discovery'; -import { getAttackDiscovery } from './get_attack_discovery'; -import { AIAssistantDataClient, AIAssistantDataClientParams } from '..'; +import { findAllAttackDiscoveries } from './find_all_attack_discoveries/find_all_attack_discoveries'; +import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id'; +import { updateAttackDiscovery } from './update_attack_discovery/update_attack_discovery'; +import { createAttackDiscovery } from './create_attack_discovery/create_attack_discovery'; +import { getAttackDiscovery } from './get_attack_discovery/get_attack_discovery'; +import { + AIAssistantDataClient, + AIAssistantDataClientParams, +} from '../../../ai_assistant_data_clients'; type AttackDiscoveryDataClientParams = AIAssistantDataClientParams; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts similarity index 98% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts index d9a37582f48b0..765d40f7a3226 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts @@ -7,7 +7,7 @@ import { estypes } from '@elastic/elasticsearch'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from './types'; +import { EsAttackDiscoverySchema } from '../types'; export const transformESSearchToAttackDiscovery = ( response: estypes.SearchResponse diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts index 4a17c50e06af4..08be262fede5a 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts @@ -6,7 +6,7 @@ */ import { AttackDiscoveryStatus, Provider } from '@kbn/elastic-assistant-common'; -import { EsReplacementSchema } from '../conversations/types'; +import { EsReplacementSchema } from '../../../ai_assistant_data_clients/conversations/types'; export interface EsAttackDiscoverySchema { '@timestamp': string; @@ -53,7 +53,7 @@ export interface CreateAttackDiscoverySchema { title: string; timestamp: string; details_markdown: string; - entity_summary_markdown: string; + entity_summary_markdown?: string; mitre_attack_tactics?: string[]; summary_markdown: string; id?: string; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts similarity index 97% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts index 24deda445f320..8d98839c092aa 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts @@ -7,7 +7,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import { getAttackDiscovery } from './get_attack_discovery'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; import { updateAttackDiscovery } from './update_attack_discovery'; import { AttackDiscoveryResponse, @@ -15,7 +15,7 @@ import { AttackDiscoveryUpdateProps, } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -jest.mock('./get_attack_discovery'); +jest.mock('../get_attack_discovery/get_attack_discovery'); const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); const user = { diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts index 73a386bbb4362..c810a71c5f1a3 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts @@ -14,8 +14,8 @@ import { UUID, } from '@kbn/elastic-assistant-common'; import * as uuid from 'uuid'; -import { EsReplacementSchema } from '../conversations/types'; -import { getAttackDiscovery } from './get_attack_discovery'; +import { EsReplacementSchema } from '../../../../ai_assistant_data_clients/conversations/types'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; export interface UpdateAttackDiscoverySchema { id: UUID; @@ -25,7 +25,7 @@ export interface UpdateAttackDiscoverySchema { title: string; timestamp: string; details_markdown: string; - entity_summary_markdown: string; + entity_summary_markdown?: string; mitre_attack_tactics?: string[]; summary_markdown: string; id?: string; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts index 706da7197f31a..b9e4f85a800a0 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts @@ -10,14 +10,41 @@ import { GetDefaultAssistantGraphParams, DefaultAssistantGraph, } from './default_assistant_graph/graph'; +import { + DefaultAttackDiscoveryGraph, + GetDefaultAttackDiscoveryGraphParams, + getDefaultAttackDiscoveryGraph, +} from '../../attack_discovery/graphs/default_attack_discovery_graph'; export type GetAssistantGraph = (params: GetDefaultAssistantGraphParams) => DefaultAssistantGraph; +export type GetAttackDiscoveryGraph = ( + params: GetDefaultAttackDiscoveryGraphParams +) => DefaultAttackDiscoveryGraph; + +export type GraphType = 'assistant' | 'attack-discovery'; + +export interface AssistantGraphMetadata { + getDefaultAssistantGraph: GetAssistantGraph; + graphType: 'assistant'; +} + +export interface AttackDiscoveryGraphMetadata { + getDefaultAttackDiscoveryGraph: GetAttackDiscoveryGraph; + graphType: 'attack-discovery'; +} + +export type GraphMetadata = AssistantGraphMetadata | AttackDiscoveryGraphMetadata; /** * Map of the different Assistant Graphs. Useful for running evaluations. */ -export const ASSISTANT_GRAPH_MAP: Record = { - DefaultAssistantGraph: getDefaultAssistantGraph, - // TODO: Support additional graphs - // AttackDiscoveryGraph: getDefaultAssistantGraph, +export const ASSISTANT_GRAPH_MAP: Record = { + DefaultAssistantGraph: { + getDefaultAssistantGraph, + graphType: 'assistant', + }, + DefaultAttackDiscoveryGraph: { + getDefaultAttackDiscoveryGraph, + graphType: 'attack-discovery', + }, }; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts similarity index 85% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts index 74cf160c43ffe..ce07d66b9606e 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts @@ -8,15 +8,24 @@ import { getAttackDiscoveryRoute } from './get_attack_discovery'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { serverMock } from '../../__mocks__/server'; -import { requestContextMock } from '../../__mocks__/request_context'; +import { serverMock } from '../../../__mocks__/server'; +import { requestContextMock } from '../../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { getAttackDiscoveryRequest } from '../../__mocks__/request'; -import { getAttackDiscoveryStats, updateAttackDiscoveryLastViewedAt } from './helpers'; -jest.mock('./helpers'); +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoveryRequest } from '../../../__mocks__/request'; +import { getAttackDiscoveryStats, updateAttackDiscoveryLastViewedAt } from '../helpers/helpers'; + +jest.mock('../helpers/helpers', () => { + const original = jest.requireActual('../helpers/helpers'); + + return { + ...original, + getAttackDiscoveryStats: jest.fn(), + updateAttackDiscoveryLastViewedAt: jest.fn(), + }; +}); const mockStats = { newConnectorResultsCount: 2, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts index 09b2df98fe090..e3756b10a3fb3 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts @@ -14,10 +14,10 @@ import { } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateAttackDiscoveryLastViewedAt, getAttackDiscoveryStats } from './helpers'; -import { ATTACK_DISCOVERY_BY_CONNECTOR_ID } from '../../../common/constants'; -import { buildResponse } from '../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { updateAttackDiscoveryLastViewedAt, getAttackDiscoveryStats } from '../helpers/helpers'; +import { ATTACK_DISCOVERY_BY_CONNECTOR_ID } from '../../../../common/constants'; +import { buildResponse } from '../../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../../types'; export const getAttackDiscoveryRoute = (router: IRouter) => { router.versioned diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts deleted file mode 100644 index d5eaf7d159618..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts +++ /dev/null @@ -1,805 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AuthenticatedUser } from '@kbn/core-security-common'; -import moment from 'moment'; -import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; - -import { - REQUIRED_FOR_ATTACK_DISCOVERY, - addGenerationInterval, - attackDiscoveryStatus, - getAssistantToolParams, - handleToolError, - updateAttackDiscoveryStatusToCanceled, - updateAttackDiscoveryStatusToRunning, - updateAttackDiscoveries, - getAttackDiscoveryStats, -} from './helpers'; -import { ActionsClientLlm } from '@kbn/langchain/server'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; -import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { loggerMock } from '@kbn/logging-mocks'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { - AttackDiscoveryPostRequestBody, - ExecuteConnectorRequestBody, -} from '@kbn/elastic-assistant-common'; -import { coreMock } from '@kbn/core/server/mocks'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; - -import { - getAnonymizationFieldMock, - getUpdateAnonymizationFieldSchemaMock, -} from '../../__mocks__/anonymization_fields_schema.mock'; - -jest.mock('lodash/fp', () => ({ - uniq: jest.fn((arr) => Array.from(new Set(arr))), -})); - -jest.mock('@kbn/securitysolution-es-utils', () => ({ - transformError: jest.fn((err) => err), -})); -jest.mock('@kbn/langchain/server', () => ({ - ActionsClientLlm: jest.fn(), -})); -jest.mock('../evaluate/utils', () => ({ - getLangSmithTracer: jest.fn().mockReturnValue([]), -})); -jest.mock('../utils', () => ({ - getLlmType: jest.fn().mockReturnValue('llm-type'), -})); -const findAttackDiscoveryByConnectorId = jest.fn(); -const updateAttackDiscovery = jest.fn(); -const createAttackDiscovery = jest.fn(); -const getAttackDiscovery = jest.fn(); -const findAllAttackDiscoveries = jest.fn(); -const mockDataClient = { - findAttackDiscoveryByConnectorId, - updateAttackDiscovery, - createAttackDiscovery, - getAttackDiscovery, - findAllAttackDiscoveries, -} as unknown as AttackDiscoveryDataClient; -const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); -const mockLogger = loggerMock.create(); -const mockTelemetry = coreMock.createSetup().analytics; -const mockError = new Error('Test error'); - -const mockAuthenticatedUser = { - username: 'user', - profile_uid: '1234', - authentication_realm: { - type: 'my_realm_type', - name: 'my_realm_name', - }, -} as AuthenticatedUser; - -const mockApiConfig = { - connectorId: 'connector-id', - actionTypeId: '.bedrock', - model: 'model', - provider: OpenAiProviderType.OpenAi, -}; - -const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockRequest: KibanaRequest = {} as unknown as KibanaRequest< - unknown, - unknown, - any, // eslint-disable-line @typescript-eslint/no-explicit-any - any // eslint-disable-line @typescript-eslint/no-explicit-any ->; - -describe('helpers', () => { - const date = '2024-03-28T22:27:28.000Z'; - beforeAll(() => { - jest.useFakeTimers(); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - beforeEach(() => { - jest.clearAllMocks(); - jest.setSystemTime(new Date(date)); - getAttackDiscovery.mockResolvedValue(mockCurrentAd); - updateAttackDiscovery.mockResolvedValue({}); - }); - describe('getAssistantToolParams', () => { - const alertsIndexPattern = '.alerts-security.alerts-default'; - const esClient = elasticsearchClientMock.createElasticsearchClient(); - const actionsClient = actionsClientMock.create(); - const langChainTimeout = 1000; - const latestReplacements = {}; - const llm = new ActionsClientLlm({ - actionsClient, - connectorId: 'test-connecter-id', - llmType: 'bedrock', - logger: mockLogger, - temperature: 0, - timeout: 580000, - }); - const onNewReplacements = jest.fn(); - const size = 20; - - const mockParams = { - actionsClient, - alertsIndexPattern: 'alerts-*', - anonymizationFields: [{ id: '1', field: 'field1', allowed: true, anonymized: true }], - apiConfig: mockApiConfig, - esClient: mockEsClient, - connectorTimeout: 1000, - langChainTimeout: 2000, - langSmithProject: 'project', - langSmithApiKey: 'api-key', - logger: mockLogger, - latestReplacements: {}, - onNewReplacements: jest.fn(), - request: {} as KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >, - size: 10, - }; - - it('should return formatted assistant tool params', () => { - const result = getAssistantToolParams(mockParams); - - expect(ActionsClientLlm).toHaveBeenCalledWith( - expect.objectContaining({ - connectorId: 'connector-id', - llmType: 'llm-type', - }) - ); - expect(result.anonymizationFields).toEqual([ - ...mockParams.anonymizationFields, - ...REQUIRED_FOR_ATTACK_DISCOVERY, - ]); - }); - - it('returns the expected AssistantToolParams when anonymizationFields are provided', () => { - const anonymizationFields = [ - getAnonymizationFieldMock(getUpdateAnonymizationFieldSchemaMock()), - ]; - - const result = getAssistantToolParams({ - actionsClient, - alertsIndexPattern, - apiConfig: mockApiConfig, - anonymizationFields, - connectorTimeout: 1000, - latestReplacements, - esClient, - langChainTimeout, - logger: mockLogger, - onNewReplacements, - request: mockRequest, - size, - }); - - expect(result).toEqual({ - alertsIndexPattern, - anonymizationFields: [...anonymizationFields, ...REQUIRED_FOR_ATTACK_DISCOVERY], - isEnabledKnowledgeBase: false, - chain: undefined, - esClient, - langChainTimeout, - llm, - logger: mockLogger, - onNewReplacements, - replacements: latestReplacements, - request: mockRequest, - size, - }); - }); - - it('returns the expected AssistantToolParams when anonymizationFields is undefined', () => { - const anonymizationFields = undefined; - - const result = getAssistantToolParams({ - actionsClient, - alertsIndexPattern, - apiConfig: mockApiConfig, - anonymizationFields, - connectorTimeout: 1000, - latestReplacements, - esClient, - langChainTimeout, - logger: mockLogger, - onNewReplacements, - request: mockRequest, - size, - }); - - expect(result).toEqual({ - alertsIndexPattern, - anonymizationFields: [...REQUIRED_FOR_ATTACK_DISCOVERY], - isEnabledKnowledgeBase: false, - chain: undefined, - esClient, - langChainTimeout, - llm, - logger: mockLogger, - onNewReplacements, - replacements: latestReplacements, - request: mockRequest, - size, - }); - }); - - describe('addGenerationInterval', () => { - const generationInterval = { date: '2024-01-01T00:00:00Z', durationMs: 1000 }; - const existingIntervals = [ - { date: '2024-01-02T00:00:00Z', durationMs: 2000 }, - { date: '2024-01-03T00:00:00Z', durationMs: 3000 }, - ]; - - it('should add new interval and maintain length within MAX_GENERATION_INTERVALS', () => { - const result = addGenerationInterval(existingIntervals, generationInterval); - expect(result.length).toBeLessThanOrEqual(5); - expect(result).toContain(generationInterval); - }); - - it('should remove the oldest interval if exceeding MAX_GENERATION_INTERVALS', () => { - const longExistingIntervals = [...Array(5)].map((_, i) => ({ - date: `2024-01-0${i + 2}T00:00:00Z`, - durationMs: (i + 2) * 1000, - })); - const result = addGenerationInterval(longExistingIntervals, generationInterval); - expect(result.length).toBe(5); - expect(result).not.toContain(longExistingIntervals[4]); - }); - }); - - describe('updateAttackDiscoveryStatusToRunning', () => { - it('should update existing attack discovery to running', async () => { - const existingAd = { id: 'existing-id', backingIndex: 'index' }; - findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); - updateAttackDiscovery.mockResolvedValue(existingAd); - - const result = await updateAttackDiscoveryStatusToRunning( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig - ); - - expect(findAttackDiscoveryByConnectorId).toHaveBeenCalledWith({ - connectorId: mockApiConfig.connectorId, - authenticatedUser: mockAuthenticatedUser, - }); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: expect.objectContaining({ - status: attackDiscoveryStatus.running, - }), - authenticatedUser: mockAuthenticatedUser, - }); - expect(result).toEqual({ attackDiscoveryId: existingAd.id, currentAd: existingAd }); - }); - - it('should create a new attack discovery if none exists', async () => { - const newAd = { id: 'new-id', backingIndex: 'index' }; - findAttackDiscoveryByConnectorId.mockResolvedValue(null); - createAttackDiscovery.mockResolvedValue(newAd); - - const result = await updateAttackDiscoveryStatusToRunning( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig - ); - - expect(createAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryCreate: expect.objectContaining({ - status: attackDiscoveryStatus.running, - }), - authenticatedUser: mockAuthenticatedUser, - }); - expect(result).toEqual({ attackDiscoveryId: newAd.id, currentAd: newAd }); - }); - - it('should throw an error if updating or creating attack discovery fails', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(null); - createAttackDiscovery.mockResolvedValue(null); - - await expect( - updateAttackDiscoveryStatusToRunning(mockDataClient, mockAuthenticatedUser, mockApiConfig) - ).rejects.toThrow('Could not create attack discovery for connectorId: connector-id'); - }); - }); - - describe('updateAttackDiscoveryStatusToCanceled', () => { - const existingAd = { - id: 'existing-id', - backingIndex: 'index', - status: attackDiscoveryStatus.running, - }; - it('should update existing attack discovery to canceled', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); - updateAttackDiscovery.mockResolvedValue(existingAd); - - const result = await updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ); - - expect(findAttackDiscoveryByConnectorId).toHaveBeenCalledWith({ - connectorId: mockApiConfig.connectorId, - authenticatedUser: mockAuthenticatedUser, - }); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: expect.objectContaining({ - status: attackDiscoveryStatus.canceled, - }), - authenticatedUser: mockAuthenticatedUser, - }); - expect(result).toEqual(existingAd); - }); - - it('should throw an error if attack discovery is not running', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue({ - ...existingAd, - status: attackDiscoveryStatus.succeeded, - }); - await expect( - updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ) - ).rejects.toThrow( - 'Connector id connector-id does not have a running attack discovery, and therefore cannot be canceled.' - ); - }); - - it('should throw an error if attack discovery does not exist', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(null); - await expect( - updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ) - ).rejects.toThrow('Could not find attack discovery for connector id: connector-id'); - }); - it('should throw error if updateAttackDiscovery returns null', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); - updateAttackDiscovery.mockResolvedValue(null); - - await expect( - updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ) - ).rejects.toThrow('Could not update attack discovery for connector id: connector-id'); - }); - }); - - describe('updateAttackDiscoveries', () => { - const mockAttackDiscoveryId = 'attack-discovery-id'; - const mockLatestReplacements = {}; - const mockRawAttackDiscoveries = JSON.stringify({ - alertsContextCount: 5, - attackDiscoveries: [{ alertIds: ['alert-1', 'alert-2'] }, { alertIds: ['alert-3'] }], - }); - const mockSize = 10; - const mockStartTime = moment('2024-03-28T22:25:28.000Z'); - - const mockArgs = { - apiConfig: mockApiConfig, - attackDiscoveryId: mockAttackDiscoveryId, - authenticatedUser: mockAuthenticatedUser, - dataClient: mockDataClient, - latestReplacements: mockLatestReplacements, - logger: mockLogger, - rawAttackDiscoveries: mockRawAttackDiscoveries, - size: mockSize, - startTime: mockStartTime, - telemetry: mockTelemetry, - }; - - it('should update attack discoveries and report success telemetry', async () => { - await updateAttackDiscoveries(mockArgs); - - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - alertsContextCount: 5, - attackDiscoveries: [{ alertIds: ['alert-1', 'alert-2'] }, { alertIds: ['alert-3'] }], - status: attackDiscoveryStatus.succeeded, - id: mockAttackDiscoveryId, - replacements: mockLatestReplacements, - backingIndex: mockCurrentAd.backingIndex, - generationIntervals: [ - { date, durationMs: 120000 }, - ...mockCurrentAd.generationIntervals, - ], - }, - authenticatedUser: mockAuthenticatedUser, - }); - - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_success', { - actionTypeId: mockApiConfig.actionTypeId, - alertsContextCount: 5, - alertsCount: 3, - configuredAlertsCount: mockSize, - discoveriesGenerated: 2, - durationMs: 120000, - model: mockApiConfig.model, - provider: mockApiConfig.provider, - }); - }); - - it('should update attack discoveries without generation interval if no discoveries are found', async () => { - const noDiscoveriesRaw = JSON.stringify({ - alertsContextCount: 0, - attackDiscoveries: [], - }); - - await updateAttackDiscoveries({ - ...mockArgs, - rawAttackDiscoveries: noDiscoveriesRaw, - }); - - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - alertsContextCount: 0, - attackDiscoveries: [], - status: attackDiscoveryStatus.succeeded, - id: mockAttackDiscoveryId, - replacements: mockLatestReplacements, - backingIndex: mockCurrentAd.backingIndex, - }, - authenticatedUser: mockAuthenticatedUser, - }); - - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_success', { - actionTypeId: mockApiConfig.actionTypeId, - alertsContextCount: 0, - alertsCount: 0, - configuredAlertsCount: mockSize, - discoveriesGenerated: 0, - durationMs: 120000, - model: mockApiConfig.model, - provider: mockApiConfig.provider, - }); - }); - - it('should catch and log an error if raw attack discoveries is null', async () => { - await updateAttackDiscoveries({ - ...mockArgs, - rawAttackDiscoveries: null, - }); - expect(mockLogger.error).toHaveBeenCalledTimes(1); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: 'tool returned no attack discoveries', - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - - it('should return and not call updateAttackDiscovery when getAttackDiscovery returns a canceled response', async () => { - getAttackDiscovery.mockResolvedValue({ - ...mockCurrentAd, - status: attackDiscoveryStatus.canceled, - }); - await updateAttackDiscoveries(mockArgs); - - expect(mockLogger.error).not.toHaveBeenCalled(); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - }); - - it('should log the error and report telemetry when getAttackDiscovery rejects', async () => { - getAttackDiscovery.mockRejectedValue(mockError); - await updateAttackDiscoveries(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - }); - - describe('handleToolError', () => { - const mockArgs = { - apiConfig: mockApiConfig, - attackDiscoveryId: 'discovery-id', - authenticatedUser: mockAuthenticatedUser, - backingIndex: 'backing-index', - dataClient: mockDataClient, - err: mockError, - latestReplacements: {}, - logger: mockLogger, - telemetry: mockTelemetry, - }; - - it('should log the error and update attack discovery status to failed', async () => { - await handleToolError(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - status: attackDiscoveryStatus.failed, - attackDiscoveries: [], - backingIndex: 'foo', - failureReason: 'Test error', - id: 'discovery-id', - replacements: {}, - }, - authenticatedUser: mockArgs.authenticatedUser, - }); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - - it('should log the error and report telemetry when updateAttackDiscovery rejects', async () => { - updateAttackDiscovery.mockRejectedValue(mockError); - await handleToolError(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - status: attackDiscoveryStatus.failed, - attackDiscoveries: [], - backingIndex: 'foo', - failureReason: 'Test error', - id: 'discovery-id', - replacements: {}, - }, - authenticatedUser: mockArgs.authenticatedUser, - }); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - - it('should return and not call updateAttackDiscovery when getAttackDiscovery returns a canceled response', async () => { - getAttackDiscovery.mockResolvedValue({ - ...mockCurrentAd, - status: attackDiscoveryStatus.canceled, - }); - await handleToolError(mockArgs); - - expect(mockTelemetry.reportEvent).not.toHaveBeenCalled(); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - }); - - it('should log the error and report telemetry when getAttackDiscovery rejects', async () => { - getAttackDiscovery.mockRejectedValue(mockError); - await handleToolError(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - }); - }); - describe('getAttackDiscoveryStats', () => { - const mockDiscoveries = [ - { - timestamp: '2024-06-13T17:55:11.360Z', - id: '8abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:55:11.360Z', - updatedAt: '2024-06-17T20:47:57.556Z', - lastViewedAt: '2024-06-17T20:47:57.556Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'failed', - alertsContextCount: undefined, - apiConfig: { - connectorId: 'my-bedrock-old', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: - 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', - }, - { - timestamp: '2024-06-13T17:55:11.360Z', - id: '9abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:55:11.360Z', - updatedAt: '2024-06-17T20:47:57.556Z', - lastViewedAt: '2024-06-17T20:46:57.556Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'failed', - alertsContextCount: undefined, - apiConfig: { - connectorId: 'my-bedrock-old', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: - 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', - }, - { - timestamp: '2024-06-12T19:54:50.428Z', - id: '745e005b-7248-4c08-b8b6-4cad263b4be0', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T19:54:50.428Z', - updatedAt: '2024-06-17T20:47:27.182Z', - lastViewedAt: '2024-06-17T20:27:27.182Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'running', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-gen-ai', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-13T17:50:59.409Z', - id: 'f48da2ca-b63e-4387-82d7-1423a68500aa', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:50:59.409Z', - updatedAt: '2024-06-17T20:47:59.969Z', - lastViewedAt: '2024-06-17T20:47:35.227Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'succeeded', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-gpt4o-ai', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-12T21:18:56.377Z', - id: '82fced1d-de48-42db-9f56-e45122dee017', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T21:18:56.377Z', - updatedAt: '2024-06-17T20:47:39.372Z', - lastViewedAt: '2024-06-17T20:47:39.372Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'canceled', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-bedrock', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-12T16:44:23.107Z', - id: 'a4709094-6116-484b-b096-1e8d151cb4b7', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T16:44:23.107Z', - updatedAt: '2024-06-17T20:48:16.961Z', - lastViewedAt: '2024-06-17T20:47:16.961Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'succeeded', - alertsContextCount: 0, - apiConfig: { - connectorId: 'my-gen-a2i', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [ - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: 'steph threw an error', - }, - ]; - beforeEach(() => { - findAllAttackDiscoveries.mockResolvedValue(mockDiscoveries); - }); - it('returns the formatted stats object', async () => { - const stats = await getAttackDiscoveryStats({ - authenticatedUser: mockAuthenticatedUser, - dataClient: mockDataClient, - }); - expect(stats).toEqual([ - { - hasViewed: true, - status: 'failed', - count: 0, - connectorId: 'my-bedrock-old', - }, - { - hasViewed: false, - status: 'failed', - count: 0, - connectorId: 'my-bedrock-old', - }, - { - hasViewed: false, - status: 'running', - count: 1, - connectorId: 'my-gen-ai', - }, - { - hasViewed: false, - status: 'succeeded', - count: 1, - connectorId: 'my-gpt4o-ai', - }, - { - hasViewed: true, - status: 'canceled', - count: 1, - connectorId: 'my-bedrock', - }, - { - hasViewed: false, - status: 'succeeded', - count: 4, - connectorId: 'my-gen-a2i', - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts new file mode 100644 index 0000000000000..2e0a545eb083a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts @@ -0,0 +1,273 @@ +/* + * 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 { AuthenticatedUser } from '@kbn/core-security-common'; + +import { getAttackDiscoveryStats } from './helpers'; +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; + +jest.mock('lodash/fp', () => ({ + uniq: jest.fn((arr) => Array.from(new Set(arr))), +})); + +jest.mock('@kbn/securitysolution-es-utils', () => ({ + transformError: jest.fn((err) => err), +})); +jest.mock('@kbn/langchain/server', () => ({ + ActionsClientLlm: jest.fn(), +})); +jest.mock('../../evaluate/utils', () => ({ + getLangSmithTracer: jest.fn().mockReturnValue([]), +})); +jest.mock('../../utils', () => ({ + getLlmType: jest.fn().mockReturnValue('llm-type'), +})); +const findAttackDiscoveryByConnectorId = jest.fn(); +const updateAttackDiscovery = jest.fn(); +const createAttackDiscovery = jest.fn(); +const getAttackDiscovery = jest.fn(); +const findAllAttackDiscoveries = jest.fn(); +const mockDataClient = { + findAttackDiscoveryByConnectorId, + updateAttackDiscovery, + createAttackDiscovery, + getAttackDiscovery, + findAllAttackDiscoveries, +} as unknown as AttackDiscoveryDataClient; + +const mockAuthenticatedUser = { + username: 'user', + profile_uid: '1234', + authentication_realm: { + type: 'my_realm_type', + name: 'my_realm_name', + }, +} as AuthenticatedUser; + +const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; + +describe('helpers', () => { + const date = '2024-03-28T22:27:28.000Z'; + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + beforeEach(() => { + jest.clearAllMocks(); + jest.setSystemTime(new Date(date)); + getAttackDiscovery.mockResolvedValue(mockCurrentAd); + updateAttackDiscovery.mockResolvedValue({}); + }); + + describe('getAttackDiscoveryStats', () => { + const mockDiscoveries = [ + { + timestamp: '2024-06-13T17:55:11.360Z', + id: '8abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:55:11.360Z', + updatedAt: '2024-06-17T20:47:57.556Z', + lastViewedAt: '2024-06-17T20:47:57.556Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'failed', + alertsContextCount: undefined, + apiConfig: { + connectorId: 'my-bedrock-old', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: + 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', + }, + { + timestamp: '2024-06-13T17:55:11.360Z', + id: '9abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:55:11.360Z', + updatedAt: '2024-06-17T20:47:57.556Z', + lastViewedAt: '2024-06-17T20:46:57.556Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'failed', + alertsContextCount: undefined, + apiConfig: { + connectorId: 'my-bedrock-old', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: + 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', + }, + { + timestamp: '2024-06-12T19:54:50.428Z', + id: '745e005b-7248-4c08-b8b6-4cad263b4be0', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T19:54:50.428Z', + updatedAt: '2024-06-17T20:47:27.182Z', + lastViewedAt: '2024-06-17T20:27:27.182Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'running', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-gen-ai', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-13T17:50:59.409Z', + id: 'f48da2ca-b63e-4387-82d7-1423a68500aa', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:50:59.409Z', + updatedAt: '2024-06-17T20:47:59.969Z', + lastViewedAt: '2024-06-17T20:47:35.227Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'succeeded', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-gpt4o-ai', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-12T21:18:56.377Z', + id: '82fced1d-de48-42db-9f56-e45122dee017', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T21:18:56.377Z', + updatedAt: '2024-06-17T20:47:39.372Z', + lastViewedAt: '2024-06-17T20:47:39.372Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'canceled', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-bedrock', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-12T16:44:23.107Z', + id: 'a4709094-6116-484b-b096-1e8d151cb4b7', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T16:44:23.107Z', + updatedAt: '2024-06-17T20:48:16.961Z', + lastViewedAt: '2024-06-17T20:47:16.961Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'succeeded', + alertsContextCount: 0, + apiConfig: { + connectorId: 'my-gen-a2i', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [ + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: 'steph threw an error', + }, + ]; + beforeEach(() => { + findAllAttackDiscoveries.mockResolvedValue(mockDiscoveries); + }); + it('returns the formatted stats object', async () => { + const stats = await getAttackDiscoveryStats({ + authenticatedUser: mockAuthenticatedUser, + dataClient: mockDataClient, + }); + expect(stats).toEqual([ + { + hasViewed: true, + status: 'failed', + count: 0, + connectorId: 'my-bedrock-old', + }, + { + hasViewed: false, + status: 'failed', + count: 0, + connectorId: 'my-bedrock-old', + }, + { + hasViewed: false, + status: 'running', + count: 1, + connectorId: 'my-gen-ai', + }, + { + hasViewed: false, + status: 'succeeded', + count: 1, + connectorId: 'my-gpt4o-ai', + }, + { + hasViewed: true, + status: 'canceled', + count: 1, + connectorId: 'my-bedrock', + }, + { + hasViewed: false, + status: 'succeeded', + count: 4, + connectorId: 'my-gen-a2i', + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts similarity index 55% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts index f016d6ac29118..188976f0b3f5c 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts @@ -5,38 +5,29 @@ * 2.0. */ -import { AnalyticsServiceSetup, AuthenticatedUser, KibanaRequest, Logger } from '@kbn/core/server'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { AnalyticsServiceSetup, AuthenticatedUser, Logger } from '@kbn/core/server'; import { ApiConfig, AttackDiscovery, - AttackDiscoveryPostRequestBody, AttackDiscoveryResponse, AttackDiscoveryStat, AttackDiscoveryStatus, - ExecuteConnectorRequestBody, GenerationInterval, Replacements, } from '@kbn/elastic-assistant-common'; import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { Document } from '@langchain/core/documents'; import { v4 as uuidv4 } from 'uuid'; -import { ActionsClientLlm } from '@kbn/langchain/server'; - import { Moment } from 'moment'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { ActionsClient } from '@kbn/actions-plugin/server'; import moment from 'moment/moment'; import { uniq } from 'lodash/fp'; -import { PublicMethodsOf } from '@kbn/utility-types'; -import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; -import { getLlmType } from '../utils'; -import type { GetRegisteredTools } from '../../services/app_context'; + import { ATTACK_DISCOVERY_ERROR_EVENT, ATTACK_DISCOVERY_SUCCESS_EVENT, -} from '../../lib/telemetry/event_based_telemetry'; -import { AssistantToolParams } from '../../types'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; +} from '../../../lib/telemetry/event_based_telemetry'; +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ { @@ -53,116 +44,6 @@ export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ }, ]; -export const getAssistantToolParams = ({ - actionsClient, - alertsIndexPattern, - anonymizationFields, - apiConfig, - esClient, - connectorTimeout, - langChainTimeout, - langSmithProject, - langSmithApiKey, - logger, - latestReplacements, - onNewReplacements, - request, - size, -}: { - actionsClient: PublicMethodsOf; - alertsIndexPattern: string; - anonymizationFields?: AnonymizationFieldResponse[]; - apiConfig: ApiConfig; - esClient: ElasticsearchClient; - connectorTimeout: number; - langChainTimeout: number; - langSmithProject?: string; - langSmithApiKey?: string; - logger: Logger; - latestReplacements: Replacements; - onNewReplacements: (newReplacements: Replacements) => void; - request: KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >; - size: number; -}) => { - const traceOptions = { - projectName: langSmithProject, - tracers: [ - ...getLangSmithTracer({ - apiKey: langSmithApiKey, - projectName: langSmithProject, - logger, - }), - ], - }; - - const llm = new ActionsClientLlm({ - actionsClient, - connectorId: apiConfig.connectorId, - llmType: getLlmType(apiConfig.actionTypeId), - logger, - temperature: 0, // zero temperature for attack discovery, because we want structured JSON output - timeout: connectorTimeout, - traceOptions, - }); - - return formatAssistantToolParams({ - alertsIndexPattern, - anonymizationFields, - esClient, - latestReplacements, - langChainTimeout, - llm, - logger, - onNewReplacements, - request, - size, - }); -}; - -const formatAssistantToolParams = ({ - alertsIndexPattern, - anonymizationFields, - esClient, - langChainTimeout, - latestReplacements, - llm, - logger, - onNewReplacements, - request, - size, -}: { - alertsIndexPattern: string; - anonymizationFields?: AnonymizationFieldResponse[]; - esClient: ElasticsearchClient; - langChainTimeout: number; - latestReplacements: Replacements; - llm: ActionsClientLlm; - logger: Logger; - onNewReplacements: (newReplacements: Replacements) => void; - request: KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >; - size: number; -}): Omit => ({ - alertsIndexPattern, - anonymizationFields: [...(anonymizationFields ?? []), ...REQUIRED_FOR_ATTACK_DISCOVERY], - isEnabledKnowledgeBase: false, // not required for attack discovery - esClient, - langChainTimeout, - llm, - logger, - onNewReplacements, - replacements: latestReplacements, - request, - size, -}); - export const attackDiscoveryStatus: { [k: string]: AttackDiscoveryStatus } = { canceled: 'canceled', failed: 'failed', @@ -187,7 +68,8 @@ export const addGenerationInterval = ( export const updateAttackDiscoveryStatusToRunning = async ( dataClient: AttackDiscoveryDataClient, authenticatedUser: AuthenticatedUser, - apiConfig: ApiConfig + apiConfig: ApiConfig, + alertsContextCount: number ): Promise<{ currentAd: AttackDiscoveryResponse; attackDiscoveryId: string; @@ -199,6 +81,7 @@ export const updateAttackDiscoveryStatusToRunning = async ( const currentAd = foundAttackDiscovery ? await dataClient?.updateAttackDiscovery({ attackDiscoveryUpdateProps: { + alertsContextCount, backingIndex: foundAttackDiscovery.backingIndex, id: foundAttackDiscovery.id, status: attackDiscoveryStatus.running, @@ -207,6 +90,7 @@ export const updateAttackDiscoveryStatusToRunning = async ( }) : await dataClient?.createAttackDiscovery({ attackDiscoveryCreate: { + alertsContextCount, apiConfig, attackDiscoveries: [], status: attackDiscoveryStatus.running, @@ -261,38 +145,32 @@ export const updateAttackDiscoveryStatusToCanceled = async ( return updatedAttackDiscovery; }; -const getDataFromJSON = (adStringified: string) => { - const { alertsContextCount, attackDiscoveries } = JSON.parse(adStringified); - return { alertsContextCount, attackDiscoveries }; -}; - export const updateAttackDiscoveries = async ({ + anonymizedAlerts, apiConfig, + attackDiscoveries, attackDiscoveryId, authenticatedUser, dataClient, latestReplacements, logger, - rawAttackDiscoveries, size, startTime, telemetry, }: { + anonymizedAlerts: Document[]; apiConfig: ApiConfig; + attackDiscoveries: AttackDiscovery[] | null; attackDiscoveryId: string; authenticatedUser: AuthenticatedUser; dataClient: AttackDiscoveryDataClient; latestReplacements: Replacements; logger: Logger; - rawAttackDiscoveries: string | null; size: number; startTime: Moment; telemetry: AnalyticsServiceSetup; }) => { try { - if (rawAttackDiscoveries == null) { - throw new Error('tool returned no attack discoveries'); - } const currentAd = await dataClient.getAttackDiscovery({ id: attackDiscoveryId, authenticatedUser, @@ -302,12 +180,12 @@ export const updateAttackDiscoveries = async ({ } const endTime = moment(); const durationMs = endTime.diff(startTime); - const { alertsContextCount, attackDiscoveries } = getDataFromJSON(rawAttackDiscoveries); + const alertsContextCount = anonymizedAlerts.length; const updateProps = { alertsContextCount, - attackDiscoveries, + attackDiscoveries: attackDiscoveries ?? undefined, status: attackDiscoveryStatus.succeeded, - ...(alertsContextCount === 0 || attackDiscoveries === 0 + ...(alertsContextCount === 0 ? {} : { generationIntervals: addGenerationInterval(currentAd.generationIntervals, { @@ -327,13 +205,14 @@ export const updateAttackDiscoveries = async ({ telemetry.reportEvent(ATTACK_DISCOVERY_SUCCESS_EVENT.eventType, { actionTypeId: apiConfig.actionTypeId, alertsContextCount: updateProps.alertsContextCount, - alertsCount: uniq( - updateProps.attackDiscoveries.flatMap( - (attackDiscovery: AttackDiscovery) => attackDiscovery.alertIds - ) - ).length, + alertsCount: + uniq( + updateProps.attackDiscoveries?.flatMap( + (attackDiscovery: AttackDiscovery) => attackDiscovery.alertIds + ) + ).length ?? 0, configuredAlertsCount: size, - discoveriesGenerated: updateProps.attackDiscoveries.length, + discoveriesGenerated: updateProps.attackDiscoveries?.length ?? 0, durationMs, model: apiConfig.model, provider: apiConfig.provider, @@ -350,70 +229,6 @@ export const updateAttackDiscoveries = async ({ } }; -export const handleToolError = async ({ - apiConfig, - attackDiscoveryId, - authenticatedUser, - dataClient, - err, - latestReplacements, - logger, - telemetry, -}: { - apiConfig: ApiConfig; - attackDiscoveryId: string; - authenticatedUser: AuthenticatedUser; - dataClient: AttackDiscoveryDataClient; - err: Error; - latestReplacements: Replacements; - logger: Logger; - telemetry: AnalyticsServiceSetup; -}) => { - try { - logger.error(err); - const error = transformError(err); - const currentAd = await dataClient.getAttackDiscovery({ - id: attackDiscoveryId, - authenticatedUser, - }); - - if (currentAd === null || currentAd?.status === 'canceled') { - return; - } - await dataClient.updateAttackDiscovery({ - attackDiscoveryUpdateProps: { - attackDiscoveries: [], - status: attackDiscoveryStatus.failed, - id: attackDiscoveryId, - replacements: latestReplacements, - backingIndex: currentAd.backingIndex, - failureReason: error.message, - }, - authenticatedUser, - }); - telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { - actionTypeId: apiConfig.actionTypeId, - errorMessage: error.message, - model: apiConfig.model, - provider: apiConfig.provider, - }); - } catch (updateErr) { - const updateError = transformError(updateErr); - telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { - actionTypeId: apiConfig.actionTypeId, - errorMessage: updateError.message, - model: apiConfig.model, - provider: apiConfig.provider, - }); - } -}; - -export const getAssistantTool = (getRegisteredTools: GetRegisteredTools, pluginName: string) => { - // get the attack discovery tool: - const assistantTools = getRegisteredTools(pluginName); - return assistantTools.find((tool) => tool.id === 'attack-discovery'); -}; - export const updateAttackDiscoveryLastViewedAt = async ({ connectorId, authenticatedUser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts similarity index 80% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts index 66aca77f1eb8b..9f5efbe5041d5 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts @@ -8,15 +8,23 @@ import { cancelAttackDiscoveryRoute } from './cancel_attack_discovery'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { serverMock } from '../../__mocks__/server'; -import { requestContextMock } from '../../__mocks__/request_context'; +import { serverMock } from '../../../../__mocks__/server'; +import { requestContextMock } from '../../../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { getCancelAttackDiscoveryRequest } from '../../__mocks__/request'; -import { updateAttackDiscoveryStatusToCanceled } from './helpers'; -jest.mock('./helpers'); +import { AttackDiscoveryDataClient } from '../../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; +import { getCancelAttackDiscoveryRequest } from '../../../../__mocks__/request'; +import { updateAttackDiscoveryStatusToCanceled } from '../../helpers/helpers'; + +jest.mock('../../helpers/helpers', () => { + const original = jest.requireActual('../../helpers/helpers'); + + return { + ...original, + updateAttackDiscoveryStatusToCanceled: jest.fn(), + }; +}); const { clients, context } = requestContextMock.createTools(); const server: ReturnType = serverMock.create(); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts similarity index 91% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts index 47b748c9c432a..86631708b1cf7 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts @@ -14,16 +14,16 @@ import { } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateAttackDiscoveryStatusToCanceled } from './helpers'; -import { ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID } from '../../../common/constants'; -import { buildResponse } from '../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { updateAttackDiscoveryStatusToCanceled } from '../../helpers/helpers'; +import { ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID } from '../../../../../common/constants'; +import { buildResponse } from '../../../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../../../types'; export const cancelAttackDiscoveryRoute = ( router: IRouter ) => { router.versioned - .put({ + .post({ access: 'internal', path: ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID, options: { diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx new file mode 100644 index 0000000000000..e58b67bdcc1ad --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AnalyticsServiceSetup, AuthenticatedUser, Logger } from '@kbn/core/server'; +import { ApiConfig, Replacements } from '@kbn/elastic-assistant-common'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +import { AttackDiscoveryDataClient } from '../../../../../lib/attack_discovery/persistence'; +import { attackDiscoveryStatus } from '../../../helpers/helpers'; +import { ATTACK_DISCOVERY_ERROR_EVENT } from '../../../../../lib/telemetry/event_based_telemetry'; + +export const handleGraphError = async ({ + apiConfig, + attackDiscoveryId, + authenticatedUser, + dataClient, + err, + latestReplacements, + logger, + telemetry, +}: { + apiConfig: ApiConfig; + attackDiscoveryId: string; + authenticatedUser: AuthenticatedUser; + dataClient: AttackDiscoveryDataClient; + err: Error; + latestReplacements: Replacements; + logger: Logger; + telemetry: AnalyticsServiceSetup; +}) => { + try { + logger.error(err); + const error = transformError(err); + const currentAd = await dataClient.getAttackDiscovery({ + id: attackDiscoveryId, + authenticatedUser, + }); + + if (currentAd === null || currentAd?.status === 'canceled') { + return; + } + + await dataClient.updateAttackDiscovery({ + attackDiscoveryUpdateProps: { + attackDiscoveries: [], + status: attackDiscoveryStatus.failed, + id: attackDiscoveryId, + replacements: latestReplacements, + backingIndex: currentAd.backingIndex, + failureReason: error.message, + }, + authenticatedUser, + }); + telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { + actionTypeId: apiConfig.actionTypeId, + errorMessage: error.message, + model: apiConfig.model, + provider: apiConfig.provider, + }); + } catch (updateErr) { + const updateError = transformError(updateErr); + telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { + actionTypeId: apiConfig.actionTypeId, + errorMessage: updateError.message, + model: apiConfig.model, + provider: apiConfig.provider, + }); + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx new file mode 100644 index 0000000000000..8a8c49f796500 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger } from '@kbn/core/server'; +import { ApiConfig, AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import type { Document } from '@langchain/core/documents'; + +import { getDefaultAttackDiscoveryGraph } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph'; +import { + ATTACK_DISCOVERY_GRAPH_RUN_NAME, + ATTACK_DISCOVERY_TAG, +} from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/constants'; +import { GraphState } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/types'; +import { throwIfErrorCountsExceeded } from '../throw_if_error_counts_exceeded'; +import { getLlmType } from '../../../../utils'; + +export const invokeAttackDiscoveryGraph = async ({ + actionsClient, + alertsIndexPattern, + anonymizationFields, + apiConfig, + connectorTimeout, + esClient, + langSmithProject, + langSmithApiKey, + latestReplacements, + logger, + onNewReplacements, + size, +}: { + actionsClient: PublicMethodsOf; + alertsIndexPattern: string; + anonymizationFields: AnonymizationFieldResponse[]; + apiConfig: ApiConfig; + connectorTimeout: number; + esClient: ElasticsearchClient; + langSmithProject?: string; + langSmithApiKey?: string; + latestReplacements: Replacements; + logger: Logger; + onNewReplacements: (newReplacements: Replacements) => void; + size: number; +}): Promise<{ + anonymizedAlerts: Document[]; + attackDiscoveries: AttackDiscovery[] | null; +}> => { + const llmType = getLlmType(apiConfig.actionTypeId); + const model = apiConfig.model; + const tags = [ATTACK_DISCOVERY_TAG, llmType, model].flatMap((tag) => tag ?? []); + + const traceOptions = { + projectName: langSmithProject, + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: langSmithProject, + logger, + }), + ], + }; + + const llm = new ActionsClientLlm({ + actionsClient, + connectorId: apiConfig.connectorId, + llmType, + logger, + temperature: 0, // zero temperature for attack discovery, because we want structured JSON output + timeout: connectorTimeout, + traceOptions, + }); + + if (llm == null) { + throw new Error('LLM is required for attack discoveries'); + } + + const graph = getDefaultAttackDiscoveryGraph({ + alertsIndexPattern, + anonymizationFields, + esClient, + llm, + logger, + onNewReplacements, + replacements: latestReplacements, + size, + }); + + logger?.debug(() => 'invokeAttackDiscoveryGraph: invoking the Attack discovery graph'); + + const result: GraphState = await graph.invoke( + {}, + { + callbacks: [...(traceOptions?.tracers ?? [])], + runName: ATTACK_DISCOVERY_GRAPH_RUN_NAME, + tags, + } + ); + const { + attackDiscoveries, + anonymizedAlerts, + errors, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + } = result; + + throwIfErrorCountsExceeded({ + errors, + generationAttempts, + hallucinationFailures, + logger, + maxGenerationAttempts, + maxHallucinationFailures, + }); + + return { anonymizedAlerts, attackDiscoveries }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx new file mode 100644 index 0000000000000..9cbf3fa06510d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; + +import { mockAnonymizationFields } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields'; +import { requestIsValid } from '.'; + +describe('requestIsValid', () => { + const alertsIndexPattern = '.alerts-security.alerts-default'; + const replacements = { uuid: 'original_value' }; + const size = 20; + const request = { + body: { + actionTypeId: '.bedrock', + alertsIndexPattern, + anonymizationFields: mockAnonymizationFields, + connectorId: 'test-connector-id', + replacements, + size, + subAction: 'invokeAI', + }, + } as unknown as KibanaRequest; + + it('returns false when the request is missing required anonymization parameters', () => { + const requestMissingAnonymizationParams = { + body: { + alertsIndexPattern: '.alerts-security.alerts-default', + isEnabledKnowledgeBase: false, + size: 20, + }, + } as unknown as KibanaRequest; + + const params = { + alertsIndexPattern, + request: requestMissingAnonymizationParams, // <-- missing required anonymization parameters + size, + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns false when the alertsIndexPattern is undefined', () => { + const params = { + alertsIndexPattern: undefined, // <-- alertsIndexPattern is undefined + request, + size, + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns false when size is undefined', () => { + const params = { + alertsIndexPattern, + request, + size: undefined, // <-- size is undefined + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns false when size is out of range', () => { + const params = { + alertsIndexPattern, + request, + size: 0, // <-- size is out of range + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns true if all required params are provided', () => { + const params = { + alertsIndexPattern, + request, + size, + }; + + expect(requestIsValid(params)).toBe(true); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx new file mode 100644 index 0000000000000..36487d8f6b3e2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KibanaRequest } from '@kbn/core/server'; +import { + AttackDiscoveryPostRequestBody, + ExecuteConnectorRequestBody, + sizeIsOutOfRange, +} from '@kbn/elastic-assistant-common'; + +import { requestHasRequiredAnonymizationParams } from '../../../../../lib/langchain/helpers'; + +export const requestIsValid = ({ + alertsIndexPattern, + request, + size, +}: { + alertsIndexPattern: string | undefined; + request: KibanaRequest< + unknown, + unknown, + ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody + >; + size: number | undefined; +}): boolean => + requestHasRequiredAnonymizationParams(request) && + alertsIndexPattern != null && + size != null && + !sizeIsOutOfRange(size); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts new file mode 100644 index 0000000000000..409ee2da74cd2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import * as i18n from './translations'; + +export const throwIfErrorCountsExceeded = ({ + errors, + generationAttempts, + hallucinationFailures, + logger, + maxGenerationAttempts, + maxHallucinationFailures, +}: { + errors: string[]; + generationAttempts: number; + hallucinationFailures: number; + logger?: Logger; + maxGenerationAttempts: number; + maxHallucinationFailures: number; +}): void => { + if (hallucinationFailures >= maxHallucinationFailures) { + const hallucinationFailuresError = `${i18n.MAX_HALLUCINATION_FAILURES( + hallucinationFailures + )}\n${errors.join(',\n')}`; + + logger?.error(hallucinationFailuresError); + throw new Error(hallucinationFailuresError); + } + + if (generationAttempts >= maxGenerationAttempts) { + const generationAttemptsError = `${i18n.MAX_GENERATION_ATTEMPTS( + generationAttempts + )}\n${errors.join(',\n')}`; + + logger?.error(generationAttemptsError); + throw new Error(generationAttemptsError); + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts new file mode 100644 index 0000000000000..fbe06d0e73b2a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts @@ -0,0 +1,28 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const MAX_HALLUCINATION_FAILURES = (hallucinationFailures: number) => + i18n.translate( + 'xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxHallucinationFailuresErrorMessage', + { + defaultMessage: + 'Maximum hallucination failures ({hallucinationFailures}) reached. Try sending fewer alerts to this model.', + values: { hallucinationFailures }, + } + ); + +export const MAX_GENERATION_ATTEMPTS = (generationAttempts: number) => + i18n.translate( + 'xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxGenerationAttemptsErrorMessage', + { + defaultMessage: + 'Maximum generation attempts ({generationAttempts}) reached. Try sending fewer alerts to this model.', + values: { generationAttempts }, + } + ); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts similarity index 79% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts index cbd3e6063fbd2..d50987317b0e3 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts @@ -7,22 +7,27 @@ import { AuthenticatedUser } from '@kbn/core-security-common'; import { postAttackDiscoveryRoute } from './post_attack_discovery'; -import { serverMock } from '../../__mocks__/server'; -import { requestContextMock } from '../../__mocks__/request_context'; +import { serverMock } from '../../../__mocks__/server'; +import { requestContextMock } from '../../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { postAttackDiscoveryRequest } from '../../__mocks__/request'; +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; +import { postAttackDiscoveryRequest } from '../../../__mocks__/request'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; import { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; -import { - getAssistantTool, - getAssistantToolParams, - updateAttackDiscoveryStatusToRunning, -} from './helpers'; -jest.mock('./helpers'); + +import { updateAttackDiscoveryStatusToRunning } from '../helpers/helpers'; + +jest.mock('../helpers/helpers', () => { + const original = jest.requireActual('../helpers/helpers'); + + return { + ...original, + updateAttackDiscoveryStatusToRunning: jest.fn(), + }; +}); const { clients, context } = requestContextMock.createTools(); const server: ReturnType = serverMock.create(); @@ -72,8 +77,6 @@ describe('postAttackDiscoveryRoute', () => { context.elasticAssistant.actions = actionsMock.createStart(); postAttackDiscoveryRoute(server.router); findAttackDiscoveryByConnectorId.mockResolvedValue(mockCurrentAd); - (getAssistantTool as jest.Mock).mockReturnValue({ getTool: jest.fn() }); - (getAssistantToolParams as jest.Mock).mockReturnValue({ tool: 'tool' }); (updateAttackDiscoveryStatusToRunning as jest.Mock).mockResolvedValue({ currentAd: runningAd, attackDiscoveryId: mockCurrentAd.id, @@ -117,15 +120,6 @@ describe('postAttackDiscoveryRoute', () => { }); }); - it('should handle assistantTool null response', async () => { - (getAssistantTool as jest.Mock).mockReturnValue(null); - const response = await server.inject( - postAttackDiscoveryRequest(mockRequestBody), - requestContextMock.convertContext(context) - ); - expect(response.status).toEqual(404); - }); - it('should handle updateAttackDiscoveryStatusToRunning error', async () => { (updateAttackDiscoveryStatusToRunning as jest.Mock).mockRejectedValue(new Error('Oh no!')); const response = await server.inject( diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts similarity index 79% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts index b9c680dde3d1d..b0273741bdf5e 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { type IKibanaResponse, IRouter, Logger } from '@kbn/core/server'; import { AttackDiscoveryPostRequestBody, @@ -13,20 +12,17 @@ import { ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION, Replacements, } from '@kbn/elastic-assistant-common'; +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { transformError } from '@kbn/securitysolution-es-utils'; import moment from 'moment/moment'; -import { ATTACK_DISCOVERY } from '../../../common/constants'; -import { - getAssistantTool, - getAssistantToolParams, - handleToolError, - updateAttackDiscoveries, - updateAttackDiscoveryStatusToRunning, -} from './helpers'; -import { DEFAULT_PLUGIN_NAME, getPluginNameFromRequest } from '../helpers'; -import { buildResponse } from '../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { ATTACK_DISCOVERY } from '../../../../common/constants'; +import { handleGraphError } from './helpers/handle_graph_error'; +import { updateAttackDiscoveries, updateAttackDiscoveryStatusToRunning } from '../helpers/helpers'; +import { buildResponse } from '../../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../../types'; +import { invokeAttackDiscoveryGraph } from './helpers/invoke_attack_discovery_graph'; +import { requestIsValid } from './helpers/request_is_valid'; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds @@ -85,11 +81,6 @@ export const postAttackDiscoveryRoute = ( statusCode: 500, }); } - const pluginName = getPluginNameFromRequest({ - request, - defaultPluginName: DEFAULT_PLUGIN_NAME, - logger, - }); // get parameters from the request body const alertsIndexPattern = decodeURIComponent(request.body.alertsIndexPattern); @@ -102,6 +93,19 @@ export const postAttackDiscoveryRoute = ( size, } = request.body; + if ( + !requestIsValid({ + alertsIndexPattern, + request, + size, + }) + ) { + return resp.error({ + body: 'Bad Request', + statusCode: 400, + }); + } + // get an Elasticsearch client for the authenticated user: const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -111,59 +115,45 @@ export const postAttackDiscoveryRoute = ( latestReplacements = { ...latestReplacements, ...newReplacements }; }; - const assistantTool = getAssistantTool( - (await context.elasticAssistant).getRegisteredTools, - pluginName + const { currentAd, attackDiscoveryId } = await updateAttackDiscoveryStatusToRunning( + dataClient, + authenticatedUser, + apiConfig, + size ); - if (!assistantTool) { - return response.notFound(); // attack discovery tool not found - } - - const assistantToolParams = getAssistantToolParams({ + // Don't await the results of invoking the graph; (just the metadata will be returned from the route handler): + invokeAttackDiscoveryGraph({ actionsClient, alertsIndexPattern, anonymizationFields, apiConfig, - esClient, - latestReplacements, connectorTimeout: CONNECTOR_TIMEOUT, - langChainTimeout: LANG_CHAIN_TIMEOUT, + esClient, langSmithProject, langSmithApiKey, + latestReplacements, logger, onNewReplacements, - request, size, - }); - - // invoke the attack discovery tool: - const toolInstance = assistantTool.getTool(assistantToolParams); - - const { currentAd, attackDiscoveryId } = await updateAttackDiscoveryStatusToRunning( - dataClient, - authenticatedUser, - apiConfig - ); - - toolInstance - ?.invoke('') - .then((rawAttackDiscoveries: string) => + }) + .then(({ anonymizedAlerts, attackDiscoveries }) => updateAttackDiscoveries({ + anonymizedAlerts, apiConfig, + attackDiscoveries, attackDiscoveryId, authenticatedUser, dataClient, latestReplacements, logger, - rawAttackDiscoveries, size, startTime, telemetry, }) ) .catch((err) => - handleToolError({ + handleGraphError({ apiConfig, attackDiscoveryId, authenticatedUser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts new file mode 100644 index 0000000000000..c0320c9ff6adf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ASSISTANT_GRAPH_MAP, + AssistantGraphMetadata, + AttackDiscoveryGraphMetadata, +} from '../../../lib/langchain/graphs'; + +export interface GetGraphsFromNamesResults { + attackDiscoveryGraphs: AttackDiscoveryGraphMetadata[]; + assistantGraphs: AssistantGraphMetadata[]; +} + +export const getGraphsFromNames = (graphNames: string[]): GetGraphsFromNamesResults => + graphNames.reduce( + (acc, graphName) => { + const graph = ASSISTANT_GRAPH_MAP[graphName]; + if (graph != null) { + return graph.graphType === 'assistant' + ? { ...acc, assistantGraphs: [...acc.assistantGraphs, graph] } + : { ...acc, attackDiscoveryGraphs: [...acc.attackDiscoveryGraphs, graph] }; + } + + return acc; + }, + { + attackDiscoveryGraphs: [], + assistantGraphs: [], + } + ); diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index 29a7527964677..eb12946a9b61f 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -29,6 +29,7 @@ import { createStructuredChatAgent, createToolCallingAgent, } from 'langchain/agents'; +import { omit } from 'lodash/fp'; import { buildResponse } from '../../lib/build_response'; import { AssistantDataClients } from '../../lib/langchain/executors/types'; import { AssistantToolParams, ElasticAssistantRequestHandlerContext, GetElser } from '../../types'; @@ -36,6 +37,7 @@ import { DEFAULT_PLUGIN_NAME, isV2KnowledgeBaseEnabled, performChecks } from '.. import { fetchLangSmithDataset } from './utils'; import { transformESSearchToAnonymizationFields } from '../../ai_assistant_data_clients/anonymization_fields/helpers'; import { EsAnonymizationFieldsSchema } from '../../ai_assistant_data_clients/anonymization_fields/types'; +import { evaluateAttackDiscovery } from '../../lib/attack_discovery/evaluation'; import { DefaultAssistantGraph, getDefaultAssistantGraph, @@ -47,9 +49,12 @@ import { structuredChatAgentPrompt, } from '../../lib/langchain/graphs/default_assistant_graph/prompts'; import { getLlmClass, getLlmType, isOpenSourceModel } from '../utils'; +import { getGraphsFromNames } from './get_graphs_from_names'; const DEFAULT_SIZE = 20; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes +const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds +const CONNECTOR_TIMEOUT = LANG_CHAIN_TIMEOUT - 10_000; // 9 minutes 40 seconds export const postEvaluateRoute = ( router: IRouter, @@ -106,8 +111,10 @@ export const postEvaluateRoute = ( const { alertsIndexPattern, datasetName, + evaluatorConnectorId, graphs: graphNames, langSmithApiKey, + langSmithProject, connectorIds, size, replacements, @@ -124,7 +131,9 @@ export const postEvaluateRoute = ( logger.info('postEvaluateRoute:'); logger.info(`request.query:\n${JSON.stringify(request.query, null, 2)}`); - logger.info(`request.body:\n${JSON.stringify(request.body, null, 2)}`); + logger.info( + `request.body:\n${JSON.stringify(omit(['langSmithApiKey'], request.body), null, 2)}` + ); logger.info(`Evaluation ID: ${evaluationId}`); const totalExecutions = connectorIds.length * graphNames.length * dataset.length; @@ -170,6 +179,38 @@ export const postEvaluateRoute = ( // Fetch any tools registered to the security assistant const assistantTools = assistantContext.getRegisteredTools(DEFAULT_PLUGIN_NAME); + const { attackDiscoveryGraphs } = getGraphsFromNames(graphNames); + + if (attackDiscoveryGraphs.length > 0) { + try { + // NOTE: we don't wait for the evaluation to finish here, because + // the client will retry / timeout when evaluations take too long + void evaluateAttackDiscovery({ + actionsClient, + alertsIndexPattern, + attackDiscoveryGraphs, + connectors, + connectorTimeout: CONNECTOR_TIMEOUT, + datasetName, + esClient, + evaluationId, + evaluatorConnectorId, + langSmithApiKey, + langSmithProject, + logger, + runName, + size, + }); + } catch (err) { + logger.error(() => `Error evaluating attack discovery: ${err}`); + } + + // Return early if we're only running attack discovery graphs + return response.ok({ + body: { evaluationId, success: true }, + }); + } + const graphs: Array<{ name: string; graph: DefaultAssistantGraph; diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts index 34f009e266515..0260c47b4bd29 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts @@ -21,7 +21,7 @@ export const fetchLangSmithDataset = async ( logger: Logger, langSmithApiKey?: string ): Promise => { - if (datasetName === undefined || !isLangSmithEnabled()) { + if (datasetName === undefined || (langSmithApiKey == null && !isLangSmithEnabled())) { throw new Error('LangSmith dataset name not provided or LangSmith not enabled'); } diff --git a/x-pack/plugins/elastic_assistant/server/routes/index.ts b/x-pack/plugins/elastic_assistant/server/routes/index.ts index 43e1229250f46..a6d7a4298c2b7 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/index.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/index.ts @@ -9,8 +9,8 @@ export { postActionsConnectorExecuteRoute } from './post_actions_connector_execute'; // Attack Discovery -export { postAttackDiscoveryRoute } from './attack_discovery/post_attack_discovery'; -export { getAttackDiscoveryRoute } from './attack_discovery/get_attack_discovery'; +export { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery'; +export { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery'; // Knowledge Base export { deleteKnowledgeBaseRoute } from './knowledge_base/delete_knowledge_base'; diff --git a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts index 56eb9760e442a..7898629e15b5c 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts @@ -7,9 +7,9 @@ import type { Logger } from '@kbn/core/server'; -import { cancelAttackDiscoveryRoute } from './attack_discovery/cancel_attack_discovery'; -import { getAttackDiscoveryRoute } from './attack_discovery/get_attack_discovery'; -import { postAttackDiscoveryRoute } from './attack_discovery/post_attack_discovery'; +import { cancelAttackDiscoveryRoute } from './attack_discovery/post/cancel/cancel_attack_discovery'; +import { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery'; +import { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery'; import { ElasticAssistantPluginRouter, GetElser } from '../types'; import { createConversationRoute } from './user_conversations/create_route'; import { deleteConversationRoute } from './user_conversations/delete_route'; diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index 45bd5a4149b58..e84b97ab43d7a 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -43,10 +43,10 @@ import { ActionsClientGeminiChatModel, ActionsClientLlm, } from '@kbn/langchain/server'; - import type { InferenceServerStart } from '@kbn/inference-plugin/server'; + import type { GetAIAssistantKnowledgeBaseDataClientParams } from './ai_assistant_data_clients/knowledge_base'; -import { AttackDiscoveryDataClient } from './ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from './lib/attack_discovery/persistence'; import { AIAssistantConversationsDataClient } from './ai_assistant_data_clients/conversations'; import type { GetRegisteredFeatures, GetRegisteredTools } from './services/app_context'; import { AIAssistantDataClient } from './ai_assistant_data_clients'; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx index 885ab18c879a7..dd995d115b6c3 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx @@ -6,7 +6,11 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import { + replaceAnonymizedValuesWithOriginalValues, + type AttackDiscovery, + type Replacements, +} from '@kbn/elastic-assistant-common'; import React, { useMemo } from 'react'; import { AttackDiscoveryMarkdownFormatter } from '../../attack_discovery_markdown_formatter'; @@ -23,26 +27,41 @@ const ActionableSummaryComponent: React.FC = ({ replacements, showAnonymized = false, }) => { - const entitySummaryMarkdownWithReplacements = useMemo( + const entitySummary = useMemo( () => - Object.entries(replacements ?? {}).reduce( - (acc, [key, value]) => acc.replace(key, value), - attackDiscovery.entitySummaryMarkdown - ), - [attackDiscovery.entitySummaryMarkdown, replacements] + showAnonymized + ? attackDiscovery.entitySummaryMarkdown + : replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.entitySummaryMarkdown ?? '', + replacements: { ...replacements }, + }), + + [attackDiscovery.entitySummaryMarkdown, replacements, showAnonymized] + ); + + // title will be used as a fallback if entitySummaryMarkdown is empty + const title = useMemo( + () => + showAnonymized + ? attackDiscovery.title + : replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.title, + replacements: { ...replacements }, + }), + + [attackDiscovery.title, replacements, showAnonymized] ); + const entitySummaryOrTitle = + entitySummary != null && entitySummary.length > 0 ? entitySummary : title; + return ( diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx index 2aaac0449886a..c6ac9c70e8413 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx @@ -49,8 +49,15 @@ const AttackDiscoveryPanelComponent: React.FC = ({ ); const buttonContent = useMemo( - () => , - [attackDiscovery.title] + () => ( + <Title + isLoading={false} + replacements={replacements} + showAnonymized={showAnonymized} + title={attackDiscovery.title} + /> + ), + [attackDiscovery.title, replacements, showAnonymized] ); return ( diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx index 4b0375e4fe503..13326a07adc70 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx @@ -7,20 +7,41 @@ import { EuiFlexGroup, EuiFlexItem, EuiSkeletonTitle, EuiTitle, useEuiTheme } from '@elastic/eui'; import { AssistantAvatar } from '@kbn/elastic-assistant'; +import { + replaceAnonymizedValuesWithOriginalValues, + type Replacements, +} from '@kbn/elastic-assistant-common'; import { css } from '@emotion/react'; -import React from 'react'; +import React, { useMemo } from 'react'; const AVATAR_SIZE = 24; // px interface Props { isLoading: boolean; + replacements?: Replacements; + showAnonymized?: boolean; title: string; } -const TitleComponent: React.FC<Props> = ({ isLoading, title }) => { +const TitleComponent: React.FC<Props> = ({ + isLoading, + replacements, + showAnonymized = false, + title, +}) => { const { euiTheme } = useEuiTheme(); + const titleWithReplacements = useMemo( + () => + replaceAnonymizedValuesWithOriginalValues({ + messageContent: title, + replacements: { ...replacements }, + }), + + [replacements, title] + ); + return ( <EuiFlexGroup alignItems="center" data-test-subj="title" gutterSize="s"> <EuiFlexItem @@ -53,7 +74,7 @@ const TitleComponent: React.FC<Props> = ({ isLoading, title }) => { /> ) : ( <EuiTitle data-test-subj="titleText" size="xs"> - <h2>{title}</h2> + <h2>{showAnonymized ? title : titleWithReplacements}</h2> </EuiTitle> )} </EuiFlexItem> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts b/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts index 5309ef1de6bb2..0ae524c25ee95 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts @@ -56,7 +56,7 @@ export const getAttackDiscoveryMarkdown = ({ replacements?: Replacements; }): string => { const title = getMarkdownFields(attackDiscovery.title); - const entitySummaryMarkdown = getMarkdownFields(attackDiscovery.entitySummaryMarkdown); + const entitySummaryMarkdown = getMarkdownFields(attackDiscovery.entitySummaryMarkdown ?? ''); const summaryMarkdown = getMarkdownFields(attackDiscovery.summaryMarkdown); const detailsMarkdown = getMarkdownFields(attackDiscovery.detailsMarkdown); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx b/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx index 874a4d1c99ded..ab0a5ac4ede96 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx @@ -106,7 +106,9 @@ export const usePollApi = ({ ...attackDiscovery, id: attackDiscovery.id ?? uuid.v4(), detailsMarkdown: replaceNewlineLiterals(attackDiscovery.detailsMarkdown), - entitySummaryMarkdown: replaceNewlineLiterals(attackDiscovery.entitySummaryMarkdown), + entitySummaryMarkdown: replaceNewlineLiterals( + attackDiscovery.entitySummaryMarkdown ?? '' + ), summaryMarkdown: replaceNewlineLiterals(attackDiscovery.summaryMarkdown), })), }; @@ -123,7 +125,7 @@ export const usePollApi = ({ const rawResponse = await http.fetch( `/internal/elastic_assistant/attack_discovery/cancel/${connectorId}`, { - method: 'PUT', + method: 'POST', version: ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION, } ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx index 5dd4cb8fc4267..533b95bf7087f 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx @@ -52,7 +52,7 @@ const AnimatedCounterComponent: React.FC<Props> = ({ animationDurationMs = 1000 css={css` height: 32px; margin-right: ${euiTheme.size.xs}; - width: ${count < 100 ? 40 : 53}px; + width: ${count < 100 ? 40 : 60}px; `} data-test-subj="animatedCounter" ref={d3Ref} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx index 56b2205b28726..0707950383046 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx @@ -16,6 +16,8 @@ jest.mock('../../../assistant/use_assistant_availability'); describe('EmptyPrompt', () => { const alertsCount = 20; + const aiConnectorsCount = 2; + const attackDiscoveriesCount = 0; const onGenerate = jest.fn(); beforeEach(() => { @@ -33,6 +35,8 @@ describe('EmptyPrompt', () => { <TestProviders> <EmptyPrompt alertsCount={alertsCount} + aiConnectorsCount={aiConnectorsCount} + attackDiscoveriesCount={attackDiscoveriesCount} isLoading={false} isDisabled={false} onGenerate={onGenerate} @@ -69,8 +73,34 @@ describe('EmptyPrompt', () => { }); describe('when loading is true', () => { - const isLoading = true; + beforeEach(() => { + (useAssistantAvailability as jest.Mock).mockReturnValue({ + hasAssistantPrivilege: true, + isAssistantEnabled: true, + }); + + render( + <TestProviders> + <EmptyPrompt + aiConnectorsCount={2} // <-- non-null + attackDiscoveriesCount={0} // <-- no discoveries + alertsCount={alertsCount} + isLoading={true} // <-- loading + isDisabled={false} + onGenerate={onGenerate} + /> + </TestProviders> + ); + }); + + it('returns null', () => { + const emptyPrompt = screen.queryByTestId('emptyPrompt'); + expect(emptyPrompt).not.toBeInTheDocument(); + }); + }); + + describe('when aiConnectorsCount is null', () => { beforeEach(() => { (useAssistantAvailability as jest.Mock).mockReturnValue({ hasAssistantPrivilege: true, @@ -80,8 +110,10 @@ describe('EmptyPrompt', () => { render( <TestProviders> <EmptyPrompt + aiConnectorsCount={null} // <-- null + attackDiscoveriesCount={0} // <-- no discoveries alertsCount={alertsCount} - isLoading={isLoading} + isLoading={false} // <-- not loading isDisabled={false} onGenerate={onGenerate} /> @@ -89,10 +121,38 @@ describe('EmptyPrompt', () => { ); }); - it('disables the generate button while loading', () => { - const generateButton = screen.getByTestId('generate'); + it('returns null', () => { + const emptyPrompt = screen.queryByTestId('emptyPrompt'); - expect(generateButton).toBeDisabled(); + expect(emptyPrompt).not.toBeInTheDocument(); + }); + }); + + describe('when there are attack discoveries', () => { + beforeEach(() => { + (useAssistantAvailability as jest.Mock).mockReturnValue({ + hasAssistantPrivilege: true, + isAssistantEnabled: true, + }); + + render( + <TestProviders> + <EmptyPrompt + aiConnectorsCount={2} // <-- non-null + attackDiscoveriesCount={7} // there are discoveries + alertsCount={alertsCount} + isLoading={false} // <-- not loading + isDisabled={false} + onGenerate={onGenerate} + /> + </TestProviders> + ); + }); + + it('returns null', () => { + const emptyPrompt = screen.queryByTestId('emptyPrompt'); + + expect(emptyPrompt).not.toBeInTheDocument(); }); }); @@ -109,6 +169,8 @@ describe('EmptyPrompt', () => { <TestProviders> <EmptyPrompt alertsCount={alertsCount} + aiConnectorsCount={2} // <-- non-null + attackDiscoveriesCount={0} // <-- no discoveries isLoading={false} isDisabled={isDisabled} onGenerate={onGenerate} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx index 75c8533efcc92..3d89f5be87030 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx @@ -7,7 +7,6 @@ import { AssistantAvatar } from '@kbn/elastic-assistant'; import { - EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, @@ -15,24 +14,28 @@ import { EuiLink, EuiSpacer, EuiText, - EuiToolTip, useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; import React, { useMemo } from 'react'; import { AnimatedCounter } from './animated_counter'; +import { Generate } from '../generate'; import * as i18n from './translations'; interface Props { + aiConnectorsCount: number | null; // null when connectors are not configured alertsCount: number; + attackDiscoveriesCount: number; isDisabled?: boolean; isLoading: boolean; onGenerate: () => void; } const EmptyPromptComponent: React.FC<Props> = ({ + aiConnectorsCount, alertsCount, + attackDiscoveriesCount, isLoading, isDisabled = false, onGenerate, @@ -110,25 +113,13 @@ const EmptyPromptComponent: React.FC<Props> = ({ ); const actions = useMemo(() => { - const disabled = isLoading || isDisabled; - - return ( - <EuiToolTip - content={disabled ? i18n.SELECT_A_CONNECTOR : null} - data-test-subj="generateTooltip" - > - <EuiButton - color="primary" - data-test-subj="generate" - disabled={disabled} - onClick={onGenerate} - > - {i18n.GENERATE} - </EuiButton> - </EuiToolTip> - ); + return <Generate isLoading={isLoading} isDisabled={isDisabled} onGenerate={onGenerate} />; }, [isDisabled, isLoading, onGenerate]); + if (isLoading || aiConnectorsCount == null || attackDiscoveriesCount > 0) { + return null; + } + return ( <EuiFlexGroup alignItems="center" diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts new file mode 100644 index 0000000000000..e2c7018ef5826 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + showEmptyPrompt, + showFailurePrompt, + showNoAlertsPrompt, + showWelcomePrompt, +} from '../../../helpers'; + +export const showEmptyStates = ({ + aiConnectorsCount, + alertsContextCount, + attackDiscoveriesCount, + connectorId, + failureReason, + isLoading, +}: { + aiConnectorsCount: number | null; + alertsContextCount: number | null; + attackDiscoveriesCount: number; + connectorId: string | undefined; + failureReason: string | null; + isLoading: boolean; +}): boolean => { + const showWelcome = showWelcomePrompt({ aiConnectorsCount, isLoading }); + const showFailure = showFailurePrompt({ connectorId, failureReason, isLoading }); + const showNoAlerts = showNoAlertsPrompt({ alertsContextCount, connectorId, isLoading }); + const showEmpty = showEmptyPrompt({ aiConnectorsCount, attackDiscoveriesCount, isLoading }); + + return showWelcome || showFailure || showNoAlerts || showEmpty; +}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx index 3b5b87ada83ec..9eacd696a2ff1 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { render, screen } from '@testing-library/react'; import React from 'react'; @@ -18,7 +19,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = 0; // <-- no connectors configured const alertsContextCount = null; - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = false; @@ -29,12 +29,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -59,7 +59,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 0; // <-- no alerts to analyze - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; @@ -70,12 +69,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -104,8 +103,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 10; - const alertsCount = 10; - const attackDiscoveriesCount = 10; + const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -115,12 +113,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={"you're a failure"} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -149,8 +147,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 10; - const alertsCount = 10; - const attackDiscoveriesCount = 10; + const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const failureReason = 'this failure should NOT be displayed, because we are loading'; // <-- failureReason is provided const isLoading = true; // <-- loading data @@ -161,12 +158,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={failureReason} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -195,8 +192,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const alertsCount = 0; // <-- no alerts contributed to attack discoveries - const attackDiscoveriesCount = 0; // <-- no attack discoveries were generated from the alerts + const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -206,12 +202,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -240,7 +236,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = null; // <-- no connectors configured const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = false; @@ -251,12 +246,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -287,7 +282,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = 0; // <-- no connectors configured (welcome prompt should be shown if not loading) const alertsContextCount = null; - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = true; // <-- loading data @@ -298,12 +292,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -338,8 +332,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const alertsCount = 10; // <-- alerts contributed to attack discoveries - const attackDiscoveriesCount = 3; // <-- attack discoveries were generated from the alerts + const attackDiscoveriesCount = 7; // <-- attack discoveries are present const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -349,12 +342,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx index 49b4557c72192..a083ec7b77fdd 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx @@ -9,51 +9,55 @@ import React from 'react'; import { Failure } from '../failure'; import { EmptyPrompt } from '../empty_prompt'; -import { showEmptyPrompt, showNoAlertsPrompt, showWelcomePrompt } from '../helpers'; +import { showFailurePrompt, showNoAlertsPrompt, showWelcomePrompt } from '../helpers'; import { NoAlerts } from '../no_alerts'; import { Welcome } from '../welcome'; interface Props { - aiConnectorsCount: number | null; - alertsContextCount: number | null; - alertsCount: number; + aiConnectorsCount: number | null; // null when connectors are not configured + alertsContextCount: number | null; // null when unavailable for the current connector attackDiscoveriesCount: number; connectorId: string | undefined; failureReason: string | null; isLoading: boolean; onGenerate: () => Promise<void>; + upToAlertsCount: number; } const EmptyStatesComponent: React.FC<Props> = ({ aiConnectorsCount, alertsContextCount, - alertsCount, attackDiscoveriesCount, connectorId, failureReason, isLoading, onGenerate, + upToAlertsCount, }) => { + const isDisabled = connectorId == null; + if (showWelcomePrompt({ aiConnectorsCount, isLoading })) { return <Welcome />; - } else if (!isLoading && failureReason != null) { + } + + if (showFailurePrompt({ connectorId, failureReason, isLoading })) { return <Failure failureReason={failureReason} />; - } else if (showNoAlertsPrompt({ alertsContextCount, isLoading })) { - return <NoAlerts />; - } else if (showEmptyPrompt({ aiConnectorsCount, attackDiscoveriesCount, isLoading })) { - return ( - <EmptyPrompt - alertsCount={alertsCount} - isDisabled={connectorId == null} - isLoading={isLoading} - onGenerate={onGenerate} - /> - ); } - return null; -}; + if (showNoAlertsPrompt({ alertsContextCount, connectorId, isLoading })) { + return <NoAlerts isLoading={isLoading} isDisabled={isDisabled} onGenerate={onGenerate} />; + } -EmptyStatesComponent.displayName = 'EmptyStates'; + return ( + <EmptyPrompt + aiConnectorsCount={aiConnectorsCount} + alertsCount={upToAlertsCount} + attackDiscoveriesCount={attackDiscoveriesCount} + isDisabled={isDisabled} + isLoading={isLoading} + onGenerate={onGenerate} + /> + ); +}; export const EmptyStates = React.memo(EmptyStatesComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx index 4318f3f78536a..c9c27446fe51c 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx @@ -5,13 +5,53 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; +import { + EuiAccordion, + EuiCodeBlock, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiText, +} from '@elastic/eui'; import { css } from '@emotion/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import * as i18n from './translations'; -const FailureComponent: React.FC<{ failureReason: string }> = ({ failureReason }) => { +interface Props { + failureReason: string | null | undefined; +} + +const FailureComponent: React.FC<Props> = ({ failureReason }) => { + const Failures = useMemo(() => { + const failures = failureReason != null ? failureReason.split('\n') : ''; + const [firstFailure, ...restFailures] = failures; + + return ( + <> + <p>{firstFailure}</p> + + {restFailures.length > 0 && ( + <EuiAccordion + id="failuresFccordion" + buttonContent={i18n.DETAILS} + data-test-subj="failuresAccordion" + paddingSize="s" + > + <> + {restFailures.map((failure, i) => ( + <EuiCodeBlock fontSize="m" key={i} paddingSize="m"> + {failure} + </EuiCodeBlock> + ))} + </> + </EuiAccordion> + )} + </> + ); + }, [failureReason]); + return ( <EuiFlexGroup alignItems="center" data-test-subj="failure" direction="column"> <EuiFlexItem data-test-subj="emptyPromptContainer" grow={false}> @@ -26,7 +66,7 @@ const FailureComponent: React.FC<{ failureReason: string }> = ({ failureReason } `} data-test-subj="bodyText" > - {failureReason} + {Failures} </EuiText> } title={<h2 data-test-subj="failureTitle">{i18n.FAILURE_TITLE}</h2>} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts index b36104d202ba8..ecaa7fad240e1 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; -export const LEARN_MORE = i18n.translate( - 'xpack.securitySolution.attackDiscovery.pages.failure.learnMoreLink', +export const DETAILS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.pages.failure.detailsAccordionButton', { - defaultMessage: 'Learn more about Attack discovery', + defaultMessage: 'Details', } ); @@ -20,3 +20,10 @@ export const FAILURE_TITLE = i18n.translate( defaultMessage: 'Attack discovery generation failed', } ); + +export const LEARN_MORE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.pages.failure.learnMoreLink', + { + defaultMessage: 'Learn more about Attack discovery', + } +); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx new file mode 100644 index 0000000000000..16ed376dd3af4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton, EuiToolTip } from '@elastic/eui'; +import React from 'react'; + +import * as i18n from '../empty_prompt/translations'; + +interface Props { + isDisabled?: boolean; + isLoading: boolean; + onGenerate: () => void; +} + +const GenerateComponent: React.FC<Props> = ({ isLoading, isDisabled = false, onGenerate }) => { + const disabled = isLoading || isDisabled; + + return ( + <EuiToolTip + content={disabled ? i18n.SELECT_A_CONNECTOR : null} + data-test-subj="generateTooltip" + > + <EuiButton color="primary" data-test-subj="generate" disabled={disabled} onClick={onGenerate}> + {i18n.GENERATE} + </EuiButton> + </EuiToolTip> + ); +}; + +GenerateComponent.displayName = 'Generate'; + +export const Generate = React.memo(GenerateComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx index aee53d889c7ac..7b0688eadafef 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; @@ -31,9 +32,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onGenerate={jest.fn()} onConnectorIdSelected={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -54,9 +57,11 @@ describe('Header', () => { connectorsAreConfigured={connectorsAreConfigured} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onGenerate={jest.fn()} onConnectorIdSelected={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -77,9 +82,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={onGenerate} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -102,9 +109,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={isLoading} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -126,9 +135,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={isLoading} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={onCancel} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -150,9 +161,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx index 583bcc25d0eb6..ff170805670a6 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx @@ -9,10 +9,11 @@ import type { EuiButtonProps } from '@elastic/eui'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { ConnectorSelectorInline } from '@kbn/elastic-assistant'; +import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; +import { SettingsModal } from './settings_modal'; import { StatusBell } from './status_bell'; import * as i18n from './translations'; @@ -21,9 +22,11 @@ interface Props { connectorsAreConfigured: boolean; isLoading: boolean; isDisabledActions: boolean; + localStorageAttackDiscoveryMaxAlerts: string | undefined; onGenerate: () => void; onCancel: () => void; onConnectorIdSelected: (connectorId: string) => void; + setLocalStorageAttackDiscoveryMaxAlerts: React.Dispatch<React.SetStateAction<string | undefined>>; stats: AttackDiscoveryStats | null; } @@ -32,9 +35,11 @@ const HeaderComponent: React.FC<Props> = ({ connectorsAreConfigured, isLoading, isDisabledActions, + localStorageAttackDiscoveryMaxAlerts, onGenerate, onConnectorIdSelected, onCancel, + setLocalStorageAttackDiscoveryMaxAlerts, stats, }) => { const { euiTheme } = useEuiTheme(); @@ -68,6 +73,7 @@ const HeaderComponent: React.FC<Props> = ({ }, [isLoading, handleCancel, onGenerate] ); + return ( <EuiFlexGroup alignItems="center" @@ -78,6 +84,14 @@ const HeaderComponent: React.FC<Props> = ({ data-test-subj="header" gutterSize="none" > + <EuiFlexItem grow={false}> + <SettingsModal + connectorId={connectorId} + isLoading={isLoading} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + setLocalStorageAttackDiscoveryMaxAlerts={setLocalStorageAttackDiscoveryMaxAlerts} + /> + </EuiFlexItem> <StatusBell stats={stats} /> {connectorsAreConfigured && ( <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx new file mode 100644 index 0000000000000..b51a1fc3f85c8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SingleRangeChangeEvent } from '@kbn/elastic-assistant'; +import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; +import { + AlertsRange, + SELECT_FEWER_ALERTS, + YOUR_ANONYMIZATION_SETTINGS, +} from '@kbn/elastic-assistant'; +import React, { useCallback } from 'react'; + +import * as i18n from '../translations'; + +export const MAX_ALERTS = 500; +export const MIN_ALERTS = 50; +export const ROW_MIN_WITH = 550; // px +export const STEP = 50; + +interface Props { + maxAlerts: string; + setMaxAlerts: React.Dispatch<React.SetStateAction<string>>; +} + +const AlertsSettingsComponent: React.FC<Props> = ({ maxAlerts, setMaxAlerts }) => { + const onChangeAlertsRange = useCallback( + (e: SingleRangeChangeEvent) => { + setMaxAlerts(e.currentTarget.value); + }, + [setMaxAlerts] + ); + + return ( + <EuiForm component="form"> + <EuiFormRow hasChildLabel={false} label={i18n.ALERTS}> + <EuiFlexGroup direction="column" gutterSize="none"> + <EuiFlexItem grow={false}> + <AlertsRange + maxAlerts={MAX_ALERTS} + minAlerts={MIN_ALERTS} + onChange={onChangeAlertsRange} + step={STEP} + value={maxAlerts} + /> + <EuiSpacer size="m" /> + </EuiFlexItem> + + <EuiFlexItem grow={true}> + <EuiText color="subdued" size="xs"> + <span>{i18n.LATEST_AND_RISKIEST_OPEN_ALERTS(Number(maxAlerts))}</span> + </EuiText> + </EuiFlexItem> + + <EuiFlexItem grow={true}> + <EuiText color="subdued" size="xs"> + <span>{YOUR_ANONYMIZATION_SETTINGS}</span> + </EuiText> + </EuiFlexItem> + + <EuiFlexItem grow={true}> + <EuiText color="subdued" size="xs"> + <span>{SELECT_FEWER_ALERTS}</span> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + </EuiForm> + ); +}; + +AlertsSettingsComponent.displayName = 'AlertsSettings'; + +export const AlertsSettings = React.memo(AlertsSettingsComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx new file mode 100644 index 0000000000000..0066376a0e198 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx @@ -0,0 +1,57 @@ +/* + * 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 { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; + +import * as i18n from '../translations'; + +interface Props { + closeModal: () => void; + onReset: () => void; + onSave: () => void; +} + +const FooterComponent: React.FC<Props> = ({ closeModal, onReset, onSave }) => { + const { euiTheme } = useEuiTheme(); + + return ( + <EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty data-test-sub="reset" flush="both" onClick={onReset} size="s"> + {i18n.RESET} + </EuiButtonEmpty> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiFlexGroup alignItems="center" gutterSize="none"> + <EuiFlexItem + css={css` + margin-right: ${euiTheme.size.s}; + `} + grow={false} + > + <EuiButtonEmpty data-test-sub="cancel" onClick={closeModal} size="s"> + {i18n.CANCEL} + </EuiButtonEmpty> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiButton data-test-sub="save" fill onClick={onSave} size="s"> + {i18n.SAVE} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +FooterComponent.displayName = 'Footer'; + +export const Footer = React.memo(FooterComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx new file mode 100644 index 0000000000000..0d342c591f32b --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx @@ -0,0 +1,160 @@ +/* + * 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 { + EuiButtonIcon, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, + EuiToolTip, + EuiTourStep, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { + ATTACK_DISCOVERY_STORAGE_KEY, + DEFAULT_ASSISTANT_NAMESPACE, + DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY, +} from '@kbn/elastic-assistant'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useLocalStorage } from 'react-use'; + +import { AlertsSettings } from './alerts_settings'; +import { useSpaceId } from '../../../../common/hooks/use_space_id'; +import { Footer } from './footer'; +import { getIsTourEnabled } from './is_tour_enabled'; +import * as i18n from './translations'; + +interface Props { + connectorId: string | undefined; + isLoading: boolean; + localStorageAttackDiscoveryMaxAlerts: string | undefined; + setLocalStorageAttackDiscoveryMaxAlerts: React.Dispatch<React.SetStateAction<string | undefined>>; +} + +const SettingsModalComponent: React.FC<Props> = ({ + connectorId, + isLoading, + localStorageAttackDiscoveryMaxAlerts, + setLocalStorageAttackDiscoveryMaxAlerts, +}) => { + const spaceId = useSpaceId() ?? 'default'; + const modalTitleId = useGeneratedHtmlId(); + + const [maxAlerts, setMaxAlerts] = useState( + localStorageAttackDiscoveryMaxAlerts ?? `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}` + ); + + const [isModalVisible, setIsModalVisible] = useState(false); + const showModal = useCallback(() => { + setMaxAlerts(localStorageAttackDiscoveryMaxAlerts ?? `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`); + + setIsModalVisible(true); + }, [localStorageAttackDiscoveryMaxAlerts]); + const closeModal = useCallback(() => setIsModalVisible(false), []); + + const onReset = useCallback(() => setMaxAlerts(`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`), []); + + const onSave = useCallback(() => { + setLocalStorageAttackDiscoveryMaxAlerts(maxAlerts); + closeModal(); + }, [closeModal, maxAlerts, setLocalStorageAttackDiscoveryMaxAlerts]); + + const [showSettingsTour, setShowSettingsTour] = useLocalStorage<boolean>( + `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY}.v8.16`, + true + ); + const onTourFinished = useCallback(() => setShowSettingsTour(() => false), [setShowSettingsTour]); + const [tourDelayElapsed, setTourDelayElapsed] = useState(false); + + useEffect(() => { + // visible EuiTourStep anchors don't follow the button when the layout changes (i.e. when the connectors finish loading) + const timeout = setTimeout(() => setTourDelayElapsed(true), 10000); + return () => clearTimeout(timeout); + }, []); + + const onSettingsClicked = useCallback(() => { + showModal(); + setShowSettingsTour(() => false); + }, [setShowSettingsTour, showModal]); + + const SettingsButton = useMemo( + () => ( + <EuiToolTip content={i18n.SETTINGS}> + <EuiButtonIcon + aria-label={i18n.SETTINGS} + data-test-subj="settings" + iconType="gear" + onClick={onSettingsClicked} + /> + </EuiToolTip> + ), + [onSettingsClicked] + ); + + const isTourEnabled = getIsTourEnabled({ + connectorId, + isLoading, + tourDelayElapsed, + showSettingsTour, + }); + + return ( + <> + {isTourEnabled ? ( + <EuiTourStep + anchorPosition="downCenter" + content={ + <> + <EuiText size="s"> + <p> + <span>{i18n.ATTACK_DISCOVERY_SENDS_MORE_ALERTS}</span> + <br /> + <span>{i18n.CONFIGURE_YOUR_SETTINGS_HERE}</span> + </p> + </EuiText> + </> + } + isStepOpen={showSettingsTour} + minWidth={300} + onFinish={onTourFinished} + step={1} + stepsTotal={1} + subtitle={i18n.RECENT_ATTACK_DISCOVERY_IMPROVEMENTS} + title={i18n.SEND_MORE_ALERTS} + > + {SettingsButton} + </EuiTourStep> + ) : ( + <>{SettingsButton}</> + )} + + {isModalVisible && ( + <EuiModal aria-labelledby={modalTitleId} data-test-subj="modal" onClose={closeModal}> + <EuiModalHeader> + <EuiModalHeaderTitle id={modalTitleId}>{i18n.SETTINGS}</EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <AlertsSettings maxAlerts={maxAlerts} setMaxAlerts={setMaxAlerts} /> + </EuiModalBody> + + <EuiModalFooter> + <Footer closeModal={closeModal} onReset={onReset} onSave={onSave} /> + </EuiModalFooter> + </EuiModal> + )} + </> + ); +}; + +SettingsModalComponent.displayName = 'SettingsModal'; + +export const SettingsModal = React.memo(SettingsModalComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts new file mode 100644 index 0000000000000..7f2f356114902 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getIsTourEnabled = ({ + connectorId, + isLoading, + tourDelayElapsed, + showSettingsTour, +}: { + connectorId: string | undefined; + isLoading: boolean; + tourDelayElapsed: boolean; + showSettingsTour: boolean | undefined; +}): boolean => !isLoading && connectorId != null && tourDelayElapsed && !!showSettingsTour; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts new file mode 100644 index 0000000000000..dc42db84f2d8a --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts @@ -0,0 +1,81 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ALERTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.alertsLabel', + { + defaultMessage: 'Alerts', + } +); + +export const ATTACK_DISCOVERY_SENDS_MORE_ALERTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.attackDiscoverySendsMoreAlertsTourText', + { + defaultMessage: 'Attack discovery sends more alerts as context.', + } +); + +export const CANCEL = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.cancelButton', + { + defaultMessage: 'Cancel', + } +); + +export const CONFIGURE_YOUR_SETTINGS_HERE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.configureYourSettingsHereTourText', + { + defaultMessage: 'Configure your settings here.', + } +); + +export const LATEST_AND_RISKIEST_OPEN_ALERTS = (alertsCount: number) => + i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.latestAndRiskiestOpenAlertsLabel', + { + defaultMessage: + 'Send Attack discovery information about your {alertsCount} newest and riskiest open or acknowledged alerts.', + values: { alertsCount }, + } + ); + +export const SAVE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.saveButton', + { + defaultMessage: 'Save', + } +); + +export const SEND_MORE_ALERTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.tourTitle', + { + defaultMessage: 'Send more alerts', + } +); + +export const SETTINGS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.settingsLabel', + { + defaultMessage: 'Settings', + } +); + +export const RECENT_ATTACK_DISCOVERY_IMPROVEMENTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.tourSubtitle', + { + defaultMessage: 'Recent Attack discovery improvements', + } +); + +export const RESET = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.resetLabel', + { + defaultMessage: 'Reset', + } +); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts index e94687611ea8f..c7e1c579418b4 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts @@ -12,6 +12,7 @@ describe('helpers', () => { it('returns true when isLoading is false and alertsContextCount is 0', () => { const result = showNoAlertsPrompt({ alertsContextCount: 0, + connectorId: 'test', isLoading: false, }); @@ -21,6 +22,7 @@ describe('helpers', () => { it('returns false when isLoading is true', () => { const result = showNoAlertsPrompt({ alertsContextCount: 0, + connectorId: 'test', isLoading: true, }); @@ -30,6 +32,7 @@ describe('helpers', () => { it('returns false when alertsContextCount is null', () => { const result = showNoAlertsPrompt({ alertsContextCount: null, + connectorId: 'test', isLoading: false, }); @@ -39,6 +42,7 @@ describe('helpers', () => { it('returns false when alertsContextCount greater than 0', () => { const result = showNoAlertsPrompt({ alertsContextCount: 20, + connectorId: 'test', isLoading: false, }); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts index e3d3be963bacd..b990c3ccf1555 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts @@ -75,11 +75,14 @@ export const getErrorToastText = ( export const showNoAlertsPrompt = ({ alertsContextCount, + connectorId, isLoading, }: { alertsContextCount: number | null; + connectorId: string | undefined; isLoading: boolean; -}): boolean => !isLoading && alertsContextCount != null && alertsContextCount === 0; +}): boolean => + connectorId != null && !isLoading && alertsContextCount != null && alertsContextCount === 0; export const showWelcomePrompt = ({ aiConnectorsCount, @@ -111,12 +114,26 @@ export const showLoading = ({ loadingConnectorId: string | null; }): boolean => isLoading && (loadingConnectorId === connectorId || attackDiscoveriesCount === 0); -export const showSummary = ({ +export const showSummary = (attackDiscoveriesCount: number) => attackDiscoveriesCount > 0; + +export const showFailurePrompt = ({ connectorId, - attackDiscoveriesCount, - loadingConnectorId, + failureReason, + isLoading, }: { connectorId: string | undefined; - attackDiscoveriesCount: number; - loadingConnectorId: string | null; -}): boolean => loadingConnectorId !== connectorId && attackDiscoveriesCount > 0; + failureReason: string | null; + isLoading: boolean; +}): boolean => connectorId != null && !isLoading && failureReason != null; + +export const getSize = ({ + defaultMaxAlerts, + localStorageAttackDiscoveryMaxAlerts, +}: { + defaultMaxAlerts: number; + localStorageAttackDiscoveryMaxAlerts: string | undefined; +}): number => { + const size = Number(localStorageAttackDiscoveryMaxAlerts); + + return isNaN(size) || size <= 0 ? defaultMaxAlerts : size; +}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx index ea5c16fc3cbba..e55b2fe5083b6 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; import { css } from '@emotion/react'; import { ATTACK_DISCOVERY_STORAGE_KEY, DEFAULT_ASSISTANT_NAMESPACE, + DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + MAX_ALERTS_LOCAL_STORAGE_KEY, useAssistantContext, useLoadConnectors, } from '@kbn/elastic-assistant'; @@ -23,23 +25,16 @@ import { HeaderPage } from '../../common/components/header_page'; import { useSpaceId } from '../../common/hooks/use_space_id'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { Header } from './header'; -import { - CONNECTOR_ID_LOCAL_STORAGE_KEY, - getInitialIsOpen, - showLoading, - showSummary, -} from './helpers'; -import { AttackDiscoveryPanel } from '../attack_discovery_panel'; -import { EmptyStates } from './empty_states'; +import { CONNECTOR_ID_LOCAL_STORAGE_KEY, getSize, showLoading } from './helpers'; import { LoadingCallout } from './loading_callout'; import { PageTitle } from './page_title'; -import { Summary } from './summary'; +import { Results } from './results'; import { useAttackDiscovery } from '../use_attack_discovery'; const AttackDiscoveryPageComponent: React.FC = () => { const spaceId = useSpaceId() ?? 'default'; - const { http, knowledgeBase } = useAssistantContext(); + const { http } = useAssistantContext(); const { data: aiConnectors } = useLoadConnectors({ http, }); @@ -54,6 +49,12 @@ const AttackDiscoveryPageComponent: React.FC = () => { `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${CONNECTOR_ID_LOCAL_STORAGE_KEY}` ); + const [localStorageAttackDiscoveryMaxAlerts, setLocalStorageAttackDiscoveryMaxAlerts] = + useLocalStorage<string>( + `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${MAX_ALERTS_LOCAL_STORAGE_KEY}`, + `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}` + ); + const [connectorId, setConnectorId] = React.useState<string | undefined>( localStorageAttackDiscoveryConnectorId ); @@ -78,6 +79,10 @@ const AttackDiscoveryPageComponent: React.FC = () => { } = useAttackDiscovery({ connectorId, setLoadingConnectorId, + size: getSize({ + defaultMaxAlerts: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + localStorageAttackDiscoveryMaxAlerts, + }), }); // get last updated from the cached attack discoveries if it exists: @@ -159,9 +164,11 @@ const AttackDiscoveryPageComponent: React.FC = () => { isLoading={isLoading} // disable header actions before post request has completed isDisabledActions={isLoadingPost} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} onConnectorIdSelected={onConnectorIdSelected} onGenerate={onGenerate} onCancel={onCancel} + setLocalStorageAttackDiscoveryMaxAlerts={setLocalStorageAttackDiscoveryMaxAlerts} stats={stats} /> <EuiSpacer size="m" /> @@ -170,68 +177,37 @@ const AttackDiscoveryPageComponent: React.FC = () => { <EuiEmptyPrompt data-test-subj="animatedLogo" icon={animatedLogo} /> ) : ( <> - {showSummary({ + {showLoading({ attackDiscoveriesCount, connectorId, + isLoading: isLoading || isLoadingPost, loadingConnectorId, - }) && ( - <Summary + }) ? ( + <LoadingCallout + alertsContextCount={alertsContextCount} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + approximateFutureTime={approximateFutureTime} + connectorIntervals={connectorIntervals} + /> + ) : ( + <Results + aiConnectorsCount={aiConnectors?.length ?? null} + alertsContextCount={alertsContextCount} alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} - lastUpdated={selectedConnectorLastUpdated} + connectorId={connectorId} + failureReason={failureReason} + isLoading={isLoading} + isLoadingPost={isLoadingPost} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + onGenerate={onGenerate} onToggleShowAnonymized={onToggleShowAnonymized} + selectedConnectorAttackDiscoveries={selectedConnectorAttackDiscoveries} + selectedConnectorLastUpdated={selectedConnectorLastUpdated} + selectedConnectorReplacements={selectedConnectorReplacements} showAnonymized={showAnonymized} /> )} - - <> - {showLoading({ - attackDiscoveriesCount, - connectorId, - isLoading: isLoading || isLoadingPost, - loadingConnectorId, - }) ? ( - <LoadingCallout - alertsCount={knowledgeBase.latestAlerts} - approximateFutureTime={approximateFutureTime} - connectorIntervals={connectorIntervals} - /> - ) : ( - selectedConnectorAttackDiscoveries.map((attackDiscovery, i) => ( - <React.Fragment key={attackDiscovery.id}> - <AttackDiscoveryPanel - attackDiscovery={attackDiscovery} - initialIsOpen={getInitialIsOpen(i)} - showAnonymized={showAnonymized} - replacements={selectedConnectorReplacements} - /> - <EuiSpacer size="l" /> - </React.Fragment> - )) - )} - </> - <EuiFlexGroup - css={css` - max-height: 100%; - min-height: 100%; - `} - direction="column" - gutterSize="none" - > - <EuiSpacer size="xxl" /> - <EuiFlexItem grow={false}> - <EmptyStates - aiConnectorsCount={aiConnectors?.length ?? null} - alertsContextCount={alertsContextCount} - alertsCount={knowledgeBase.latestAlerts} - attackDiscoveriesCount={attackDiscoveriesCount} - failureReason={failureReason} - connectorId={connectorId} - isLoading={isLoading || isLoadingPost} - onGenerate={onGenerate} - /> - </EuiFlexItem> - </EuiFlexGroup> </> )} <SpyRoute pageName={SecurityPageName.attackDiscovery} /> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx index af6efafb3c1dd..f755017288300 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx @@ -29,9 +29,10 @@ describe('LoadingCallout', () => { ]; const defaultProps = { - alertsCount: 30, + alertsContextCount: 30, approximateFutureTime: new Date(), connectorIntervals, + localStorageAttackDiscoveryMaxAlerts: '50', }; it('renders the animated loading icon', () => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx index 7e392e3165711..aee8241ec73fc 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx @@ -20,13 +20,15 @@ const BACKGROUND_COLOR_DARK = '#0B2030'; const BORDER_COLOR_DARK = '#0B2030'; interface Props { - alertsCount: number; + alertsContextCount: number | null; approximateFutureTime: Date | null; connectorIntervals: GenerationInterval[]; + localStorageAttackDiscoveryMaxAlerts: string | undefined; } const LoadingCalloutComponent: React.FC<Props> = ({ - alertsCount, + alertsContextCount, + localStorageAttackDiscoveryMaxAlerts, approximateFutureTime, connectorIntervals, }) => { @@ -46,11 +48,14 @@ const LoadingCalloutComponent: React.FC<Props> = ({ `} grow={false} > - <LoadingMessages alertsCount={alertsCount} /> + <LoadingMessages + alertsContextCount={alertsContextCount} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + /> </EuiFlexItem> </EuiFlexGroup> ), - [alertsCount, euiTheme.size.m] + [alertsContextCount, euiTheme.size.m, localStorageAttackDiscoveryMaxAlerts] ); const isDarkMode = theme.getTheme().darkMode === true; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts new file mode 100644 index 0000000000000..9a3061272ca15 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getLoadingCalloutAlertsCount = ({ + alertsContextCount, + defaultMaxAlerts, + localStorageAttackDiscoveryMaxAlerts, +}: { + alertsContextCount: number | null; + defaultMaxAlerts: number; + localStorageAttackDiscoveryMaxAlerts: string | undefined; +}): number => { + if (alertsContextCount != null && !isNaN(alertsContextCount) && alertsContextCount > 0) { + return alertsContextCount; + } + + const size = Number(localStorageAttackDiscoveryMaxAlerts); + + return isNaN(size) || size <= 0 ? defaultMaxAlerts : size; +}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx index 250a25055791a..8b3f174792c5e 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx @@ -16,7 +16,7 @@ describe('LoadingMessages', () => { it('renders the expected loading message', () => { render( <TestProviders> - <LoadingMessages alertsCount={20} /> + <LoadingMessages alertsContextCount={20} localStorageAttackDiscoveryMaxAlerts={'30'} /> </TestProviders> ); const attackDiscoveryGenerationInProgress = screen.getByTestId( @@ -31,7 +31,7 @@ describe('LoadingMessages', () => { it('renders the loading message with the expected alerts count', () => { render( <TestProviders> - <LoadingMessages alertsCount={20} /> + <LoadingMessages alertsContextCount={20} localStorageAttackDiscoveryMaxAlerts={'30'} /> </TestProviders> ); const aiCurrentlyAnalyzing = screen.getByTestId('aisCurrentlyAnalyzing'); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx index 9acd7b4d2dbbf..1a84771e5c635 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx @@ -7,22 +7,34 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import React from 'react'; import { useKibana } from '../../../../common/lib/kibana'; +import { getLoadingCalloutAlertsCount } from './get_loading_callout_alerts_count'; import * as i18n from '../translations'; const TEXT_COLOR = '#343741'; interface Props { - alertsCount: number; + alertsContextCount: number | null; + localStorageAttackDiscoveryMaxAlerts: string | undefined; } -const LoadingMessagesComponent: React.FC<Props> = ({ alertsCount }) => { +const LoadingMessagesComponent: React.FC<Props> = ({ + alertsContextCount, + localStorageAttackDiscoveryMaxAlerts, +}) => { const { theme } = useKibana().services; const isDarkMode = theme.getTheme().darkMode === true; + const alertsCount = getLoadingCalloutAlertsCount({ + alertsContextCount, + defaultMaxAlerts: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + localStorageAttackDiscoveryMaxAlerts, + }); + return ( <EuiFlexGroup data-test-subj="loadingMessages" direction="column" gutterSize="none"> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx index 6c2640623e370..6c6bbfb25cb7f 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx @@ -13,7 +13,7 @@ import { ATTACK_DISCOVERY_ONLY, LEARN_MORE, NO_ALERTS_TO_ANALYZE } from './trans describe('NoAlerts', () => { beforeEach(() => { - render(<NoAlerts />); + render(<NoAlerts isDisabled={false} isLoading={false} onGenerate={jest.fn()} />); }); it('renders the avatar', () => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx index a7b0cd929336b..ace75f568bf3d 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx @@ -17,8 +17,15 @@ import { import React, { useMemo } from 'react'; import * as i18n from './translations'; +import { Generate } from '../generate'; -const NoAlertsComponent: React.FC = () => { +interface Props { + isDisabled: boolean; + isLoading: boolean; + onGenerate: () => void; +} + +const NoAlertsComponent: React.FC<Props> = ({ isDisabled, isLoading, onGenerate }) => { const title = useMemo( () => ( <EuiFlexGroup @@ -83,6 +90,14 @@ const NoAlertsComponent: React.FC = () => { {i18n.LEARN_MORE} </EuiLink> </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiSpacer size="m" /> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <Generate isDisabled={isDisabled} isLoading={isLoading} onGenerate={onGenerate} /> + </EuiFlexItem> </EuiFlexGroup> ); }; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx new file mode 100644 index 0000000000000..6e3e43127e711 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx @@ -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 { EuiSpacer } from '@elastic/eui'; +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; +import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import React from 'react'; + +import { AttackDiscoveryPanel } from '../../attack_discovery_panel'; +import { EmptyStates } from '../empty_states'; +import { showEmptyStates } from '../empty_states/helpers/show_empty_states'; +import { getInitialIsOpen, showSummary } from '../helpers'; +import { Summary } from '../summary'; + +interface Props { + aiConnectorsCount: number | null; // null when connectors are not configured + alertsContextCount: number | null; // null when unavailable for the current connector + alertsCount: number; + attackDiscoveriesCount: number; + connectorId: string | undefined; + failureReason: string | null; + isLoading: boolean; + isLoadingPost: boolean; + localStorageAttackDiscoveryMaxAlerts: string | undefined; + onGenerate: () => Promise<void>; + onToggleShowAnonymized: () => void; + selectedConnectorAttackDiscoveries: AttackDiscovery[]; + selectedConnectorLastUpdated: Date | null; + selectedConnectorReplacements: Replacements; + showAnonymized: boolean; +} + +const ResultsComponent: React.FC<Props> = ({ + aiConnectorsCount, + alertsContextCount, + alertsCount, + attackDiscoveriesCount, + connectorId, + failureReason, + isLoading, + isLoadingPost, + localStorageAttackDiscoveryMaxAlerts, + onGenerate, + onToggleShowAnonymized, + selectedConnectorAttackDiscoveries, + selectedConnectorLastUpdated, + selectedConnectorReplacements, + showAnonymized, +}) => { + if ( + showEmptyStates({ + aiConnectorsCount, + alertsContextCount, + attackDiscoveriesCount, + connectorId, + failureReason, + isLoading, + }) + ) { + return ( + <> + <EuiSpacer size="xxl" /> + <EmptyStates + aiConnectorsCount={aiConnectorsCount} + alertsContextCount={alertsContextCount} + attackDiscoveriesCount={attackDiscoveriesCount} + failureReason={failureReason} + connectorId={connectorId} + isLoading={isLoading || isLoadingPost} + onGenerate={onGenerate} + upToAlertsCount={Number( + localStorageAttackDiscoveryMaxAlerts ?? DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS + )} + /> + </> + ); + } + + return ( + <> + {showSummary(attackDiscoveriesCount) && ( + <Summary + alertsCount={alertsCount} + attackDiscoveriesCount={attackDiscoveriesCount} + lastUpdated={selectedConnectorLastUpdated} + onToggleShowAnonymized={onToggleShowAnonymized} + showAnonymized={showAnonymized} + /> + )} + + {selectedConnectorAttackDiscoveries.map((attackDiscovery, i) => ( + <React.Fragment key={attackDiscovery.id}> + <AttackDiscoveryPanel + attackDiscovery={attackDiscovery} + initialIsOpen={getInitialIsOpen(i)} + showAnonymized={showAnonymized} + replacements={selectedConnectorReplacements} + /> + <EuiSpacer size="l" /> + </React.Fragment> + ))} + </> + ); +}; + +ResultsComponent.displayName = 'Results'; + +export const Results = React.memo(ResultsComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts index f2fd17d5978b7..cc0034c90d1fa 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { omit } from 'lodash/fp'; @@ -132,9 +133,7 @@ describe('getRequestBody', () => { }, ], }; - const knowledgeBase = { - latestAlerts: 20, - }; + const traceOptions = { apmUrl: '/app/apm', langSmithProject: '', @@ -145,7 +144,7 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern, anonymizationFields, - knowledgeBase, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -160,8 +159,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, replacements: {}, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); @@ -170,7 +169,7 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern: undefined, anonymizationFields, - knowledgeBase, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -185,8 +184,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, replacements: {}, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); @@ -195,7 +194,7 @@ describe('getRequestBody', () => { const withLangSmith = { alertsIndexPattern, anonymizationFields, - knowledgeBase, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions: { apmUrl: '/app/apm', langSmithProject: 'A project', @@ -216,7 +215,7 @@ describe('getRequestBody', () => { }, langSmithApiKey: withLangSmith.traceOptions.langSmithApiKey, langSmithProject: withLangSmith.traceOptions.langSmithProject, - size: knowledgeBase.latestAlerts, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, replacements: {}, subAction: 'invokeAI', }); @@ -226,8 +225,8 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern, anonymizationFields, - knowledgeBase, selectedConnector: connector, // <-- selectedConnector is provided + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -242,7 +241,7 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, replacements: {}, subAction: 'invokeAI', }); @@ -258,8 +257,8 @@ describe('getRequestBody', () => { alertsIndexPattern, anonymizationFields, genAiConfig, // <-- genAiConfig is provided - knowledgeBase, selectedConnector: connector, // <-- selectedConnector is provided + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -274,8 +273,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, replacements: {}, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts index 97eb132bdaaeb..7aa9bfdd118d9 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts @@ -5,10 +5,7 @@ * 2.0. */ -import type { - KnowledgeBaseConfig, - TraceOptions, -} from '@kbn/elastic-assistant/impl/assistant/types'; +import type { TraceOptions } from '@kbn/elastic-assistant/impl/assistant/types'; import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import type { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; @@ -60,8 +57,8 @@ export const getRequestBody = ({ alertsIndexPattern, anonymizationFields, genAiConfig, - knowledgeBase, selectedConnector, + size, traceOptions, }: { alertsIndexPattern: string | undefined; @@ -83,7 +80,7 @@ export const getRequestBody = ({ }>; }; genAiConfig?: GenAiConfig; - knowledgeBase: KnowledgeBaseConfig; + size: number; selectedConnector?: ActionConnector; traceOptions: TraceOptions; }): AttackDiscoveryPostRequestBody => ({ @@ -95,8 +92,8 @@ export const getRequestBody = ({ langSmithApiKey: isEmpty(traceOptions?.langSmithApiKey) ? undefined : traceOptions?.langSmithApiKey, - size: knowledgeBase.latestAlerts, replacements: {}, // no need to re-use replacements in the current implementation + size, subAction: 'invokeAI', // non-streaming apiConfig: { connectorId: selectedConnector?.id ?? '', diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx index 6329ce5ca699a..59659ee6d8649 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx @@ -106,6 +106,8 @@ const mockAttackDiscoveries = [ const setLoadingConnectorId = jest.fn(); const setStatus = jest.fn(); +const SIZE = 20; + describe('useAttackDiscovery', () => { const mockPollApi = { cancelAttackDiscovery: jest.fn(), @@ -126,7 +128,11 @@ describe('useAttackDiscovery', () => { it('initializes with correct default values', () => { const { result } = renderHook(() => - useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId }) + useAttackDiscovery({ + connectorId: 'test-id', + setLoadingConnectorId, + size: 20, + }) ); expect(result.current.alertsContextCount).toBeNull(); @@ -144,14 +150,15 @@ describe('useAttackDiscovery', () => { it('fetches attack discoveries and updates state correctly', async () => { (mockedUseKibana.services.http.fetch as jest.Mock).mockResolvedValue(mockAttackDiscoveryPost); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); + await act(async () => { await result.current.fetchAttackDiscoveries(); }); expect(mockedUseKibana.services.http.fetch).toHaveBeenCalledWith( '/internal/elastic_assistant/attack_discovery', { - body: '{"alertsIndexPattern":"alerts-index-pattern","anonymizationFields":[],"size":20,"replacements":{},"subAction":"invokeAI","apiConfig":{"connectorId":"test-id","actionTypeId":".gen-ai"}}', + body: `{"alertsIndexPattern":"alerts-index-pattern","anonymizationFields":[],"replacements":{},"size":${SIZE},"subAction":"invokeAI","apiConfig":{"connectorId":"test-id","actionTypeId":".gen-ai"}}`, method: 'POST', version: '1', } @@ -167,7 +174,7 @@ describe('useAttackDiscovery', () => { const error = new Error(errorMessage); (mockedUseKibana.services.http.fetch as jest.Mock).mockRejectedValue(error); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); await act(async () => { await result.current.fetchAttackDiscoveries(); @@ -184,7 +191,11 @@ describe('useAttackDiscovery', () => { it('sets loading state based on poll status', async () => { (usePollApi as jest.Mock).mockReturnValue({ ...mockPollApi, status: 'running' }); const { result } = renderHook(() => - useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId }) + useAttackDiscovery({ + connectorId: 'test-id', + setLoadingConnectorId, + size: SIZE, + }) ); expect(result.current.isLoading).toBe(true); @@ -202,7 +213,7 @@ describe('useAttackDiscovery', () => { }, status: 'succeeded', }); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); expect(result.current.alertsContextCount).toEqual(20); // this is set from usePollApi @@ -227,7 +238,7 @@ describe('useAttackDiscovery', () => { }, status: 'failed', }); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); expect(result.current.failureReason).toEqual('something bad'); expect(result.current.isLoading).toBe(false); @@ -241,7 +252,13 @@ describe('useAttackDiscovery', () => { data: [], // <-- zero connectors configured }); - renderHook(() => useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId })); + renderHook(() => + useAttackDiscovery({ + connectorId: 'test-id', + setLoadingConnectorId, + size: SIZE, + }) + ); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx index deb1c556bdb43..4ad78981d4540 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx @@ -43,9 +43,11 @@ export interface UseAttackDiscovery { export const useAttackDiscovery = ({ connectorId, + size, setLoadingConnectorId, }: { connectorId: string | undefined; + size: number; setLoadingConnectorId?: (loadingConnectorId: string | null) => void; }): UseAttackDiscovery => { // get Kibana services and connectors @@ -75,7 +77,7 @@ export const useAttackDiscovery = ({ const [isLoading, setIsLoading] = useState(false); // get alerts index pattern and allow lists from the assistant context: - const { alertsIndexPattern, knowledgeBase, traceOptions } = useAssistantContext(); + const { alertsIndexPattern, traceOptions } = useAssistantContext(); const { data: anonymizationFields } = useFetchAnonymizationFields(); @@ -95,18 +97,11 @@ export const useAttackDiscovery = ({ alertsIndexPattern, anonymizationFields, genAiConfig, - knowledgeBase, + size, selectedConnector, traceOptions, }); - }, [ - aiConnectors, - alertsIndexPattern, - anonymizationFields, - connectorId, - knowledgeBase, - traceOptions, - ]); + }, [aiConnectors, alertsIndexPattern, anonymizationFields, connectorId, size, traceOptions]); useEffect(() => { if ( @@ -140,7 +135,7 @@ export const useAttackDiscovery = ({ useEffect(() => { if (pollData !== null && pollData.connectorId === connectorId) { if (pollData.alertsContextCount != null) setAlertsContextCount(pollData.alertsContextCount); - if (pollData.attackDiscoveries.length) { + if (pollData.attackDiscoveries.length && pollData.attackDiscoveries[0].timestamp != null) { // get last updated from timestamp, not from updatedAt since this can indicate the last time the status was updated setLastUpdated(new Date(pollData.attackDiscoveries[0].timestamp)); } diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts deleted file mode 100644 index 4d06751f57d7d..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import type { DynamicTool } from '@langchain/core/tools'; - -import { loggerMock } from '@kbn/logging-mocks'; - -import { ATTACK_DISCOVERY_TOOL } from './attack_discovery_tool'; -import { mockAnonymizationFields } from '../mock/mock_anonymization_fields'; -import { mockEmptyOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_empty_open_and_acknowledged_alerts_qery_results'; -import { mockOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_open_and_acknowledged_alerts_query_results'; - -jest.mock('langchain/chains', () => { - const mockLLMChain = jest.fn().mockImplementation(() => ({ - call: jest.fn().mockResolvedValue({ - records: [ - { - alertIds: [ - 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', - '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', - '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', - 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', - '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', - ], - detailsMarkdown: - '- Malicious Go application named "My Go Application.app" is being executed from temporary directories, likely indicating malware delivery\n- The malicious application is spawning child processes like `osascript` to display fake system dialogs and attempt to phish user credentials ({{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }}, {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }})\n- The malicious application is also executing `chmod` to make the file `unix1` executable ({{ file.path /Users/james/unix1 }})\n- `unix1` is a potentially malicious executable that is being run with suspicious arguments related to the macOS keychain ({{ process.command_line /Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!! }})\n- Multiple detections indicate the presence of malware on the host attempting credential access and execution of malicious payloads', - entitySummaryMarkdown: - 'Malicious activity detected on {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} involving user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - mitreAttackTactics: ['Credential Access', 'Execution'], - summaryMarkdown: - 'Multiple detections indicate the presence of malware on a macOS host {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} attempting credential theft and execution of malicious payloads targeting the user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - title: 'Malware Delivering Malicious Payloads on macOS', - }, - ], - }), - })); - - return { - LLMChain: mockLLMChain, - }; -}); - -describe('AttackDiscoveryTool', () => { - const alertsIndexPattern = '.alerts-security.alerts-default'; - const replacements = { uuid: 'original_value' }; - const size = 20; - const request = { - body: { - actionTypeId: '.bedrock', - alertsIndexPattern, - anonymizationFields: mockAnonymizationFields, - connectorId: 'test-connector-id', - replacements, - size, - subAction: 'invokeAI', - }, - } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; - - const esClient = { - search: jest.fn(), - } as unknown as ElasticsearchClient; - const llm = jest.fn() as unknown as ActionsClientLlm; - const logger = loggerMock.create(); - - const rest = { - anonymizationFields: mockAnonymizationFields, - isEnabledKnowledgeBase: false, - llm, - logger, - onNewReplacements: jest.fn(), - size, - }; - - beforeEach(() => { - jest.clearAllMocks(); - - (esClient.search as jest.Mock).mockResolvedValue(mockOpenAndAcknowledgedAlertsQueryResults); - }); - - describe('isSupported', () => { - it('returns false when the request is missing required anonymization parameters', () => { - const requestMissingAnonymizationParams = { - body: { - isEnabledKnowledgeBase: false, - alertsIndexPattern: '.alerts-security.alerts-default', - size: 20, - }, - } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; - - const params = { - alertsIndexPattern, - esClient, - request: requestMissingAnonymizationParams, // <-- request is missing required anonymization parameters - ...rest, - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when the alertsIndexPattern is undefined', () => { - const params = { - esClient, - request, - ...rest, - alertsIndexPattern: undefined, // <-- alertsIndexPattern is undefined - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when size is undefined', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - size: undefined, // <-- size is undefined - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when size is out of range', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - size: 0, // <-- size is out of range - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when llm is undefined', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - llm: undefined, // <-- llm is undefined - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns true if all required params are provided', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(true); - }); - }); - - describe('getTool', () => { - it('returns null when llm is undefined', () => { - const tool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - llm: undefined, // <-- llm is undefined - }); - - expect(tool).toBeNull(); - }); - - it('returns a `DynamicTool` with a `func` that calls `esClient.search()` with the expected alerts query', async () => { - const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - await tool.func(''); - - expect(esClient.search).toHaveBeenCalledWith({ - allow_no_indices: true, - body: { - _source: false, - fields: mockAnonymizationFields.map(({ field }) => ({ - field, - include_unmapped: true, - })), - query: { - bool: { - filter: [ - { - bool: { - filter: [ - { - bool: { - minimum_should_match: 1, - should: [ - { - match_phrase: { - 'kibana.alert.workflow_status': 'open', - }, - }, - { - match_phrase: { - 'kibana.alert.workflow_status': 'acknowledged', - }, - }, - ], - }, - }, - { - range: { - '@timestamp': { - format: 'strict_date_optional_time', - gte: 'now-24h', - lte: 'now', - }, - }, - }, - ], - must: [], - must_not: [ - { - exists: { - field: 'kibana.alert.building_block_type', - }, - }, - ], - should: [], - }, - }, - ], - }, - }, - runtime_mappings: {}, - size, - sort: [ - { - 'kibana.alert.risk_score': { - order: 'desc', - }, - }, - { - '@timestamp': { - order: 'desc', - }, - }, - ], - }, - ignore_unavailable: true, - index: [alertsIndexPattern], - }); - }); - - it('returns a `DynamicTool` with a `func` returns an empty attack discoveries array when getAnonymizedAlerts returns no alerts', async () => { - (esClient.search as jest.Mock).mockResolvedValue( - mockEmptyOpenAndAcknowledgedAlertsQueryResults // <-- no alerts - ); - - const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - const result = await tool.func(''); - const expected = JSON.stringify({ alertsContextCount: 0, attackDiscoveries: [] }, null, 2); // <-- empty attack discoveries array - - expect(result).toEqual(expected); - }); - - it('returns a `DynamicTool` with a `func` that returns the expected results', async () => { - const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - await tool.func(''); - - const result = await tool.func(''); - const expected = JSON.stringify( - { - alertsContextCount: 20, - attackDiscoveries: [ - { - alertIds: [ - 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', - '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', - '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', - 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', - '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', - ], - detailsMarkdown: - '- Malicious Go application named "My Go Application.app" is being executed from temporary directories, likely indicating malware delivery\n- The malicious application is spawning child processes like `osascript` to display fake system dialogs and attempt to phish user credentials ({{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }}, {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }})\n- The malicious application is also executing `chmod` to make the file `unix1` executable ({{ file.path /Users/james/unix1 }})\n- `unix1` is a potentially malicious executable that is being run with suspicious arguments related to the macOS keychain ({{ process.command_line /Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!! }})\n- Multiple detections indicate the presence of malware on the host attempting credential access and execution of malicious payloads', - entitySummaryMarkdown: - 'Malicious activity detected on {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} involving user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - mitreAttackTactics: ['Credential Access', 'Execution'], - summaryMarkdown: - 'Multiple detections indicate the presence of malware on a macOS host {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} attempting credential theft and execution of malicious payloads targeting the user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - title: 'Malware Delivering Malicious Payloads on macOS', - }, - ], - }, - null, - 2 - ); - - expect(result).toEqual(expected); - }); - - it('returns a tool instance with the expected tags', () => { - const tool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - expect(tool.tags).toEqual(['attack-discovery']); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts deleted file mode 100644 index 264862d76b8f5..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { PromptTemplate } from '@langchain/core/prompts'; -import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; -import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; -import { LLMChain } from 'langchain/chains'; -import { OutputFixingParser } from 'langchain/output_parsers'; -import { DynamicTool } from '@langchain/core/tools'; - -import { APP_UI_ID } from '../../../../common'; -import { getAnonymizedAlerts } from './get_anonymized_alerts'; -import { getOutputParser } from './get_output_parser'; -import { sizeIsOutOfRange } from '../open_and_acknowledged_alerts/helpers'; -import { getAttackDiscoveryPrompt } from './get_attack_discovery_prompt'; - -export interface AttackDiscoveryToolParams extends AssistantToolParams { - alertsIndexPattern: string; - size: number; -} - -export const ATTACK_DISCOVERY_TOOL_DESCRIPTION = - 'Call this for attack discoveries containing `markdown` that should be displayed verbatim (with no additional processing).'; - -/** - * Returns a tool for generating attack discoveries from open and acknowledged - * alerts, or null if the request doesn't have all the required parameters. - */ -export const ATTACK_DISCOVERY_TOOL: AssistantTool = { - id: 'attack-discovery', - name: 'AttackDiscoveryTool', - description: ATTACK_DISCOVERY_TOOL_DESCRIPTION, - sourceRegister: APP_UI_ID, - isSupported: (params: AssistantToolParams): params is AttackDiscoveryToolParams => { - const { alertsIndexPattern, llm, request, size } = params; - - return ( - requestHasRequiredAnonymizationParams(request) && - alertsIndexPattern != null && - size != null && - !sizeIsOutOfRange(size) && - llm != null - ); - }, - getTool(params: AssistantToolParams) { - if (!this.isSupported(params)) return null; - - const { - alertsIndexPattern, - anonymizationFields, - esClient, - langChainTimeout, - llm, - onNewReplacements, - replacements, - size, - } = params as AttackDiscoveryToolParams; - - return new DynamicTool({ - name: 'AttackDiscoveryTool', - description: ATTACK_DISCOVERY_TOOL_DESCRIPTION, - func: async () => { - if (llm == null) { - throw new Error('LLM is required for attack discoveries'); - } - - const anonymizedAlerts = await getAnonymizedAlerts({ - alertsIndexPattern, - anonymizationFields, - esClient, - onNewReplacements, - replacements, - size, - }); - - const alertsContextCount = anonymizedAlerts.length; - if (alertsContextCount === 0) { - // No alerts to analyze, so return an empty attack discoveries array - return JSON.stringify({ alertsContextCount, attackDiscoveries: [] }, null, 2); - } - - const outputParser = getOutputParser(); - const outputFixingParser = OutputFixingParser.fromLLM(llm, outputParser); - - const prompt = new PromptTemplate({ - template: `Answer the user's question as best you can:\n{format_instructions}\n{query}`, - inputVariables: ['query'], - partialVariables: { - format_instructions: outputFixingParser.getFormatInstructions(), - }, - }); - - const answerFormattingChain = new LLMChain({ - llm, - prompt, - outputKey: 'records', - outputParser: outputFixingParser, - }); - - const result = await answerFormattingChain.call({ - query: getAttackDiscoveryPrompt({ anonymizedAlerts }), - timeout: langChainTimeout, - }); - const attackDiscoveries = result.records; - - return JSON.stringify({ alertsContextCount, attackDiscoveries }, null, 2); - }, - tags: ['attack-discovery'], - }); - }, -}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts deleted file mode 100644 index df211f0bd0a7d..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// NOTE: we ask the LLM to `provide insights`. We do NOT use the feature name, `AttackDiscovery`, in the prompt. -export const getAttackDiscoveryPrompt = ({ - anonymizedAlerts, -}: { - anonymizedAlerts: string[]; -}) => `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. - -Use context from the following open and acknowledged alerts to provide insights: - -""" -${anonymizedAlerts.join('\n\n')} -""" -`; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts deleted file mode 100644 index 5ad2cd11f817a..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { getOutputParser } from './get_output_parser'; - -describe('getOutputParser', () => { - it('returns a structured output parser with the expected format instructions', () => { - const outputParser = getOutputParser(); - - const expected = `You must format your output as a JSON value that adheres to a given \"JSON Schema\" instance. - -\"JSON Schema\" is a declarative language that allows you to annotate and validate JSON documents. - -For example, the example \"JSON Schema\" instance {{\"properties\": {{\"foo\": {{\"description\": \"a list of test words\", \"type\": \"array\", \"items\": {{\"type\": \"string\"}}}}}}, \"required\": [\"foo\"]}}}} -would match an object with one required property, \"foo\". The \"type\" property specifies \"foo\" must be an \"array\", and the \"description\" property semantically describes it as \"a list of test words\". The items within \"foo\" must be strings. -Thus, the object {{\"foo\": [\"bar\", \"baz\"]}} is a well-formatted instance of this example \"JSON Schema\". The object {{\"properties\": {{\"foo\": [\"bar\", \"baz\"]}}}} is not well-formatted. - -Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas! - -Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock: -\`\`\`json -{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"alertIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The alert IDs that the insight is based on.\"},\"detailsMarkdown\":{\"type\":\"string\",\"description\":\"A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"},\"entitySummaryMarkdown\":{\"type\":\"string\",\"description\":\"A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"mitreAttackTactics\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"An array of MITRE ATT&CK tactic for the insight, using one of the following values: Reconnaissance,Initial Access,Execution,Persistence,Privilege Escalation,Discovery,Lateral Movement,Command and Control,Exfiltration\"},\"summaryMarkdown\":{\"type\":\"string\",\"description\":\"A markdown summary of insight, using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"title\":{\"type\":\"string\",\"description\":\"A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.\"}},\"required\":[\"alertIds\",\"detailsMarkdown\",\"summaryMarkdown\",\"title\"],\"additionalProperties\":false},\"description\":\"Insights with markdown that always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\",\"$schema\":\"http://json-schema.org/draft-07/schema#\"} -\`\`\` -`; - - expect(outputParser.getFormatInstructions()).toEqual(expected); - }); -}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts deleted file mode 100644 index 3d66257f060e4..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { StructuredOutputParser } from 'langchain/output_parsers'; -import { z } from '@kbn/zod'; - -export const SYNTAX = '{{ field.name fieldValue1 fieldValue2 fieldValueN }}'; -const GOOD_SYNTAX_EXAMPLES = - 'Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }}'; - -const BAD_SYNTAX_EXAMPLES = - 'Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}'; - -const RECONNAISSANCE = 'Reconnaissance'; -const INITIAL_ACCESS = 'Initial Access'; -const EXECUTION = 'Execution'; -const PERSISTENCE = 'Persistence'; -const PRIVILEGE_ESCALATION = 'Privilege Escalation'; -const DISCOVERY = 'Discovery'; -const LATERAL_MOVEMENT = 'Lateral Movement'; -const COMMAND_AND_CONTROL = 'Command and Control'; -const EXFILTRATION = 'Exfiltration'; - -const MITRE_ATTACK_TACTICS = [ - RECONNAISSANCE, - INITIAL_ACCESS, - EXECUTION, - PERSISTENCE, - PRIVILEGE_ESCALATION, - DISCOVERY, - LATERAL_MOVEMENT, - COMMAND_AND_CONTROL, - EXFILTRATION, -] as const; - -// NOTE: we ask the LLM for `insight`s. We do NOT use the feature name, `AttackDiscovery`, in the prompt. -export const getOutputParser = () => - StructuredOutputParser.fromZodSchema( - z - .array( - z.object({ - alertIds: z.string().array().describe(`The alert IDs that the insight is based on.`), - detailsMarkdown: z - .string() - .describe( - `A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` - ), - entitySummaryMarkdown: z - .string() - .optional() - .describe( - `A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same ${SYNTAX} syntax` - ), - mitreAttackTactics: z - .string() - .array() - .optional() - .describe( - `An array of MITRE ATT&CK tactic for the insight, using one of the following values: ${MITRE_ATTACK_TACTICS.join( - ',' - )}` - ), - summaryMarkdown: z - .string() - .describe(`A markdown summary of insight, using the same ${SYNTAX} syntax`), - title: z - .string() - .describe( - 'A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.' - ), - }) - ) - .describe( - `Insights with markdown that always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` - ) - ); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/index.ts index a704aaa44d0a1..1b6e90eb7280f 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/index.ts @@ -10,7 +10,6 @@ import type { AssistantTool } from '@kbn/elastic-assistant-plugin/server'; import { NL_TO_ESQL_TOOL } from './esql/nl_to_esql_tool'; import { ALERT_COUNTS_TOOL } from './alert_counts/alert_counts_tool'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool'; -import { ATTACK_DISCOVERY_TOOL } from './attack_discovery/attack_discovery_tool'; import { KNOWLEDGE_BASE_RETRIEVAL_TOOL } from './knowledge_base/knowledge_base_retrieval_tool'; import { KNOWLEDGE_BASE_WRITE_TOOL } from './knowledge_base/knowledge_base_write_tool'; import { SECURITY_LABS_KNOWLEDGE_BASE_TOOL } from './security_labs/security_labs_tool'; @@ -22,7 +21,6 @@ export const getAssistantTools = ({ }): AssistantTool[] => { const tools = [ ALERT_COUNTS_TOOL, - ATTACK_DISCOVERY_TOOL, NL_TO_ESQL_TOOL, KNOWLEDGE_BASE_RETRIEVAL_TOOL, KNOWLEDGE_BASE_WRITE_TOOL, diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts deleted file mode 100644 index 722936a368b36..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - getRawDataOrDefault, - isRawDataValid, - MAX_SIZE, - MIN_SIZE, - sizeIsOutOfRange, -} from './helpers'; - -describe('helpers', () => { - describe('isRawDataValid', () => { - it('returns true for valid raw data', () => { - const rawData = { - field1: [1, 2, 3], // the Fields API may return a number array - field2: ['a', 'b', 'c'], // the Fields API may return a string array - }; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns true when a field array is empty', () => { - const rawData = { - field1: [1, 2, 3], // the Fields API may return a number array - field2: ['a', 'b', 'c'], // the Fields API may return a string array - field3: [], // the Fields API may return an empty array - }; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns false when a field does not have an array of values', () => { - const rawData = { - field1: [1, 2, 3], - field2: 'invalid', - }; - - expect(isRawDataValid(rawData)).toBe(false); - }); - - it('returns true for empty raw data', () => { - const rawData = {}; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns false when raw data is an unexpected type', () => { - const rawData = 1234; - - // @ts-expect-error - expect(isRawDataValid(rawData)).toBe(false); - }); - }); - - describe('getRawDataOrDefault', () => { - it('returns the raw data when it is valid', () => { - const rawData = { - field1: [1, 2, 3], - field2: ['a', 'b', 'c'], - }; - - expect(getRawDataOrDefault(rawData)).toEqual(rawData); - }); - - it('returns an empty object when the raw data is invalid', () => { - const rawData = { - field1: [1, 2, 3], - field2: 'invalid', - }; - - expect(getRawDataOrDefault(rawData)).toEqual({}); - }); - }); - - describe('sizeIsOutOfRange', () => { - it('returns true when size is undefined', () => { - const size = undefined; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns true when size is less than MIN_SIZE', () => { - const size = MIN_SIZE - 1; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns true when size is greater than MAX_SIZE', () => { - const size = MAX_SIZE + 1; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns false when size is exactly MIN_SIZE', () => { - const size = MIN_SIZE; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - - it('returns false when size is exactly MAX_SIZE', () => { - const size = MAX_SIZE; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - - it('returns false when size is within the valid range', () => { - const size = MIN_SIZE + 1; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts deleted file mode 100644 index dcb30e04e9dbc..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; - -export const MIN_SIZE = 10; -export const MAX_SIZE = 10000; - -export type MaybeRawData = SearchResponse['fields'] | undefined; // note: this is the type of the "fields" property in the ES response - -export const isRawDataValid = (rawData: MaybeRawData): rawData is Record<string, unknown[]> => - typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x])); - -export const getRawDataOrDefault = (rawData: MaybeRawData): Record<string, unknown[]> => - isRawDataValid(rawData) ? rawData : {}; - -export const sizeIsOutOfRange = (size?: number): boolean => - size == null || size < MIN_SIZE || size > MAX_SIZE; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts index 09bae1639f1b1..45587b65f5f4c 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts @@ -10,12 +10,13 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { DynamicTool } from '@langchain/core/tools'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts_tool'; -import { MAX_SIZE } from './helpers'; import type { RetrievalQAChain } from 'langchain/chains'; import { mockAlertsFieldsApi } from '@kbn/elastic-assistant-plugin/server/__mocks__/alerts'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; import { loggerMock } from '@kbn/logging-mocks'; +const MAX_SIZE = 10000; + describe('OpenAndAcknowledgedAlertsTool', () => { const alertsIndexPattern = 'alerts-index'; const esClient = { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts index d6b0ad58d8adb..cab015183f4a2 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts @@ -7,13 +7,17 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { Replacements } from '@kbn/elastic-assistant-common'; -import { getAnonymizedValue, transformRawData } from '@kbn/elastic-assistant-common'; +import { + getAnonymizedValue, + getOpenAndAcknowledgedAlertsQuery, + getRawDataOrDefault, + sizeIsOutOfRange, + transformRawData, +} from '@kbn/elastic-assistant-common'; import { DynamicStructuredTool } from '@langchain/core/tools'; import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; import { z } from '@kbn/zod'; import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; -import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query'; -import { getRawDataOrDefault, sizeIsOutOfRange } from './helpers'; import { APP_UI_ID } from '../../../../common'; export interface OpenAndAcknowledgedAlertsToolParams extends AssistantToolParams { diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 0d369f3c620c4..ce79bd061548f 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -205,7 +205,6 @@ "@kbn/search-types", "@kbn/field-utils", "@kbn/core-saved-objects-api-server-mocks", - "@kbn/langchain", "@kbn/core-analytics-browser", "@kbn/core-i18n-browser", "@kbn/core-theme-browser", From 65c72082906236fc5563e01cbaec20bd4d9983bb Mon Sep 17 00:00:00 2001 From: Thomas Neirynck <thomas@elastic.co> Date: Tue, 15 Oct 2024 11:01:46 -0400 Subject: [PATCH 37/84] [Telemetry] Add cluster stat timeout (#195793) ## Summary Increase cluster-stat timeout. Closes https://github.com/elastic/kibana/issues/192129 ~~This is a draft. Will discuss with @rudolf if this is the direction we'd like to go.~~ ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) --- .../server/telemetry_collection/constants.ts | 1 + .../get_cluster_stats.test.ts | 9 ++++++--- .../telemetry_collection/get_cluster_stats.ts | 16 +++++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/plugins/telemetry/server/telemetry_collection/constants.ts b/src/plugins/telemetry/server/telemetry_collection/constants.ts index 41629ec71c2e8..cac34967e87a3 100644 --- a/src/plugins/telemetry/server/telemetry_collection/constants.ts +++ b/src/plugins/telemetry/server/telemetry_collection/constants.ts @@ -11,3 +11,4 @@ * The timeout used by each request, whenever a timeout can be specified. */ export const TIMEOUT = '30s'; +export const CLUSTER_STAT_TIMEOUT = '60s'; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts index 16cf7b70b9df2..a517fa48e94f9 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { getClusterStats } from './get_cluster_stats'; -import { TIMEOUT } from './constants'; +import { CLUSTER_STAT_TIMEOUT } from './constants'; describe('get_cluster_stats', () => { it('uses the esClient to get the response from the `cluster.stats` API', async () => { @@ -17,12 +17,15 @@ describe('get_cluster_stats', () => { const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; esClient.cluster.stats.mockImplementationOnce( // @ts-expect-error the method only cares about the response body - async (_params = { timeout: TIMEOUT }) => { + async (_params = { timeout: CLUSTER_STAT_TIMEOUT }) => { return response; } ); const result = await getClusterStats(esClient); - expect(esClient.cluster.stats).toHaveBeenCalledWith({ timeout: TIMEOUT }); + expect(esClient.cluster.stats).toHaveBeenCalledWith( + { timeout: CLUSTER_STAT_TIMEOUT, include_remotes: true }, + { requestTimeout: CLUSTER_STAT_TIMEOUT } + ); expect(result).toStrictEqual(response); }); }); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts index 20624cb0ea516..35afcacc3d0b5 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts @@ -9,15 +9,25 @@ import { ClusterDetailsGetter } from '@kbn/telemetry-collection-manager-plugin/server'; import { ElasticsearchClient } from '@kbn/core/server'; -import { TIMEOUT } from './constants'; +import { CLUSTER_STAT_TIMEOUT } from './constants'; /** * Get the cluster stats from the connected cluster. * - * This is the equivalent to GET /_cluster/stats?timeout=30s. + * This is the equivalent to GET /_cluster/stats?timeout=60s&include_remotes=true */ export async function getClusterStats(esClient: ElasticsearchClient) { - return await esClient.cluster.stats({ timeout: TIMEOUT }); + return await esClient.cluster.stats( + { + timeout: CLUSTER_STAT_TIMEOUT, + + // @ts-expect-error + include_remotes: true, + }, + { + requestTimeout: CLUSTER_STAT_TIMEOUT, // enforce that Kibana would wait at least as long for ES to complete. + } + ); } /** From 920d782392a4ff327fb6e59ec148c82f2b142b2a Mon Sep 17 00:00:00 2001 From: Quentin Pradet <quentin.pradet@elastic.co> Date: Tue, 15 Oct 2024 19:04:54 +0400 Subject: [PATCH 38/84] [Console] Remove unused spec-to-console package (#193426) Closes https://github.com/elastic/kibana/issues/163333 ## Summary It was superseded by generate-console-definitions. ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> --- .github/CODEOWNERS | 1 - .../monorepo-packages.asciidoc | 1 - package.json | 1 - .../README.md | 2 - packages/kbn-spec-to-console/README.md | 36 ------ .../bin/spec_to_console.js | 69 ------------ packages/kbn-spec-to-console/index.js | 11 -- packages/kbn-spec-to-console/jest.config.js | 14 --- packages/kbn-spec-to-console/kibana.jsonc | 6 - .../cluster_health_autocomplete.json | 45 -------- .../lib/__fixtures__/cluster_health_spec.json | 104 ------------------ .../snapshot_get_autocomplete.json | 37 ------- .../lib/__fixtures__/snapshot_get_spec.json | 91 --------------- packages/kbn-spec-to-console/lib/convert.js | 85 -------------- .../kbn-spec-to-console/lib/convert.test.js | 21 ---- .../lib/convert/methods.js | 12 -- .../kbn-spec-to-console/lib/convert/params.js | 53 --------- .../kbn-spec-to-console/lib/convert/parts.js | 24 ---- .../kbn-spec-to-console/lib/convert/paths.js | 16 --- .../lib/replace_pattern.js | 12 -- packages/kbn-spec-to-console/package.json | 19 ---- scripts/spec_to_console.js | 10 -- tsconfig.base.json | 2 - yarn.lock | 4 - 24 files changed, 676 deletions(-) delete mode 100644 packages/kbn-spec-to-console/README.md delete mode 100644 packages/kbn-spec-to-console/bin/spec_to_console.js delete mode 100644 packages/kbn-spec-to-console/index.js delete mode 100644 packages/kbn-spec-to-console/jest.config.js delete mode 100644 packages/kbn-spec-to-console/kibana.jsonc delete mode 100644 packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_autocomplete.json delete mode 100644 packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_spec.json delete mode 100644 packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_autocomplete.json delete mode 100644 packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_spec.json delete mode 100644 packages/kbn-spec-to-console/lib/convert.js delete mode 100644 packages/kbn-spec-to-console/lib/convert.test.js delete mode 100644 packages/kbn-spec-to-console/lib/convert/methods.js delete mode 100644 packages/kbn-spec-to-console/lib/convert/params.js delete mode 100644 packages/kbn-spec-to-console/lib/convert/parts.js delete mode 100644 packages/kbn-spec-to-console/lib/convert/paths.js delete mode 100644 packages/kbn-spec-to-console/lib/replace_pattern.js delete mode 100644 packages/kbn-spec-to-console/package.json delete mode 100644 scripts/spec_to_console.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f126ad0cad658..a844a2decb292 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -898,7 +898,6 @@ packages/kbn-sort-package-json @elastic/kibana-operations packages/kbn-sort-predicates @elastic/kibana-visualizations x-pack/plugins/spaces @elastic/kibana-security x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin @elastic/kibana-security -packages/kbn-spec-to-console @elastic/kibana-management packages/kbn-sse-utils @elastic/obs-knowledge-team packages/kbn-sse-utils-client @elastic/obs-knowledge-team packages/kbn-sse-utils-server @elastic/obs-knowledge-team diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 0b97a425001ec..9e3848d3a007f 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -82,7 +82,6 @@ yarn kbn watch - @kbn/securitysolution-utils - @kbn/server-http-tools - @kbn/server-route-repository -- @kbn/spec-to-console - @kbn/std - @kbn/storybook - @kbn/telemetry-utils diff --git a/package.json b/package.json index 2ccaec7dff97e..d8a97951b897d 100644 --- a/package.json +++ b/package.json @@ -1475,7 +1475,6 @@ "@kbn/serverless-storybook-config": "link:packages/serverless/storybook/config", "@kbn/some-dev-log": "link:packages/kbn-some-dev-log", "@kbn/sort-package-json": "link:packages/kbn-sort-package-json", - "@kbn/spec-to-console": "link:packages/kbn-spec-to-console", "@kbn/stdio-dev-helpers": "link:packages/kbn-stdio-dev-helpers", "@kbn/storybook": "link:packages/kbn-storybook", "@kbn/synthetics-e2e": "link:x-pack/plugins/observability_solution/synthetics/e2e", diff --git a/packages/kbn-generate-console-definitions/README.md b/packages/kbn-generate-console-definitions/README.md index f6e7fa9a3dadc..a8b7e451612f5 100644 --- a/packages/kbn-generate-console-definitions/README.md +++ b/packages/kbn-generate-console-definitions/README.md @@ -1,8 +1,6 @@ # Generate console definitions This package is a script to generate definitions used in Console to display autocomplete suggestions. The definitions files are generated from the Elasticsearch specification [repo](https://github.com/elastic/elasticsearch-specification). -This script is -a new implementation of an old `kbn-spec-to-console` package: The old script used [JSON specs](https://github.com/elastic/elasticsearch/tree/main/rest-api-spec) in the Elasticsearch repo as the source. ## Instructions 1. Checkout the Elasticsearch specification [repo](https://github.com/elastic/elasticsearch-specification). diff --git a/packages/kbn-spec-to-console/README.md b/packages/kbn-spec-to-console/README.md deleted file mode 100644 index 20a5ee855f7f6..0000000000000 --- a/packages/kbn-spec-to-console/README.md +++ /dev/null @@ -1,36 +0,0 @@ -A mini utility to convert [Elasticsearch's REST spec](https://github.com/elastic/elasticsearch/blob/master/rest-api-spec) to Console's (Kibana) autocomplete format. - - -It is used to semi-manually update Console's autocompletion rules. - -### Retrieving the spec - -If you don't have a copy of the Elasticsearch repo on your machine, follow these steps to clone only the rest API specs - -``` -mkdir es-spec && cd es-spec -git init -git remote add origin https://github.com/elastic/elasticsearch -git config core.sparsecheckout true -echo "rest-api-spec/src/main/resources/rest-api-spec/api/*\nx-pack/plugin/src/test/resources/rest-api-spec/api/*" > .git/info/sparse-checkout -git pull --depth=1 origin master -``` - -### Usage - -At the root of the Kibana repository, run the following commands: - -```sh -yarn spec_to_console -g "<ELASTICSEARCH-REPO-FOLDER>/rest-api-spec/src/main/resources/rest-api-spec/api/*" -d "src/plugins/console/server/lib/spec_definitions/json/generated" -``` - -### Information used in Console that is not available in the REST spec - -* Request bodies -* Data fetched at runtime: indices, fields, snapshots, etc -* Ad hoc additions - -### Updating the script -When converting query params defined in the REST API specs to console autocompletion definitions, the script relies on a set of known conversion rules specified in [lib/convert/params.js](https://github.com/elastic/kibana/blob/main/packages/kbn-spec-to-console/lib/convert/params.js). -For example, `"keep_on_completion":{"type":"boolean"}` from REST API specs is converted to `"keep_on_completion": "__flag__"` in console autocomplete definitions. -When an unknown parameter type is encountered in REST API specs, the script will throw an `Unexpected type error` and the file [lib/convert/params.js](https://github.com/elastic/kibana/blob/main/packages/kbn-spec-to-console/lib/convert/params.js) needs to be updated by adding a new conversion rule. \ No newline at end of file diff --git a/packages/kbn-spec-to-console/bin/spec_to_console.js b/packages/kbn-spec-to-console/bin/spec_to_console.js deleted file mode 100644 index fb23aa43a231f..0000000000000 --- a/packages/kbn-spec-to-console/bin/spec_to_console.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const fs = require('fs'); -const path = require('path'); -const program = require('commander'); -const globby = require('globby'); -const chalk = require('chalk'); - -const packageJSON = require('../package.json'); -const convert = require('../lib/convert'); - -program - .version(packageJSON.version) - .option('-g --glob []', 'Files to convert') - .option('-d --directory []', 'Output directory') - .parse(process.argv); - -if (!program.glob) { - console.error('Expected input'); - process.exit(1); -} - -const files = globby.sync(program.glob); -const totalFilesCount = files.length; -let convertedFilesCount = 0; - -console.log(chalk.bold(`Detected files (count: ${totalFilesCount}):`)); -console.log(); -console.log(files); -console.log(); - -files.forEach((file) => { - const spec = JSON.parse(fs.readFileSync(file)); - const convertedSpec = convert(spec); - if (!Object.keys(convertedSpec).length) { - console.log( - // prettier-ignore - `${chalk.yellow('Detected')} ${chalk.grey(file)} but no endpoints were converted; ${chalk.yellow('skipping')}...` - ); - return; - } - const output = JSON.stringify(convertedSpec, null, 2); - ++convertedFilesCount; - if (program.directory) { - const outputName = path.basename(file); - const outputPath = path.resolve(program.directory, outputName); - try { - fs.mkdirSync(program.directory, { recursive: true }); - fs.writeFileSync(outputPath, output + '\n'); - } catch (e) { - console.log('Cannot write file ', e); - } - } else { - console.log(output); - } -}); - -console.log(); -// prettier-ignore -console.log(`${chalk.grey('Converted')} ${chalk.bold(`${convertedFilesCount}/${totalFilesCount}`)} ${chalk.grey('files')}`); -console.log(`Check your ${chalk.bold('git status')}.`); -console.log(); diff --git a/packages/kbn-spec-to-console/index.js b/packages/kbn-spec-to-console/index.js deleted file mode 100644 index 1f49a1e211f35..0000000000000 --- a/packages/kbn-spec-to-console/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const convert = require('./lib/convert'); -module.exports = convert; diff --git a/packages/kbn-spec-to-console/jest.config.js b/packages/kbn-spec-to-console/jest.config.js deleted file mode 100644 index 07e13eac1d4b2..0000000000000 --- a/packages/kbn-spec-to-console/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../..', - roots: ['<rootDir>/packages/kbn-spec-to-console'], -}; diff --git a/packages/kbn-spec-to-console/kibana.jsonc b/packages/kbn-spec-to-console/kibana.jsonc deleted file mode 100644 index 3cb4ef3763a33..0000000000000 --- a/packages/kbn-spec-to-console/kibana.jsonc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/spec-to-console", - "devOnly": true, - "owner": "@elastic/kibana-management" -} diff --git a/packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_autocomplete.json b/packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_autocomplete.json deleted file mode 100644 index 745d9c680bb00..0000000000000 --- a/packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_autocomplete.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "cluster.health": { - "url_params": { - "expand_wildcards": [ - "open", - "closed", - "none", - "all" - ], - "level": [ - "cluster", - "indices", - "shards" - ], - "local": "__flag__", - "master_timeout": "", - "timeout": "", - "wait_for_active_shards": "", - "wait_for_nodes": "", - "wait_for_events": [ - "immediate", - "urgent", - "high", - "normal", - "low", - "languid" - ], - "wait_for_no_relocating_shards": "__flag__", - "wait_for_no_initializing_shards": "__flag__", - "wait_for_status": [ - "green", - "yellow", - "red" - ] - }, - "methods": [ - "GET" - ], - "patterns": [ - "_cluster/health", - "_cluster/health/{index}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/cluster-health.html" - } -} diff --git a/packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_spec.json b/packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_spec.json deleted file mode 100644 index 7911a8e244218..0000000000000 --- a/packages/kbn-spec-to-console/lib/__fixtures__/cluster_health_spec.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "cluster.health":{ - "documentation":{ - "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/cluster-health.html", - "description":"Returns basic information about the health of the cluster." - }, - "stability":"stable", - "url":{ - "paths":[ - { - "path":"/_cluster/health", - "methods":[ - "GET" - ] - }, - { - "path":"/_cluster/health/{index}", - "methods":[ - "GET" - ], - "parts":{ - "index":{ - "type":"list", - "description":"Limit the information returned to a specific index" - } - } - } - ] - }, - "params":{ - "expand_wildcards":{ - "type":"enum", - "options":[ - "open", - "closed", - "none", - "all" - ], - "default":"all", - "description":"Whether to expand wildcard expression to concrete indices that are open, closed or both." - }, - "level":{ - "type":"enum", - "options":[ - "cluster", - "indices", - "shards" - ], - "default":"cluster", - "description":"Specify the level of detail for returned information" - }, - "local":{ - "type":"boolean", - "description":"Return local information, do not retrieve the state from master node (default: false)" - }, - "master_timeout":{ - "type":"time", - "description":"Explicit operation timeout for connection to master node" - }, - "timeout":{ - "type":"time", - "description":"Explicit operation timeout" - }, - "wait_for_active_shards":{ - "type":"string", - "description":"Wait until the specified number of shards is active" - }, - "wait_for_nodes":{ - "type":"string", - "description":"Wait until the specified number of nodes is available" - }, - "wait_for_events":{ - "type":"enum", - "options":[ - "immediate", - "urgent", - "high", - "normal", - "low", - "languid" - ], - "description":"Wait until all currently queued events with the given priority are processed" - }, - "wait_for_no_relocating_shards":{ - "type":"boolean", - "description":"Whether to wait until there are no relocating shards in the cluster" - }, - "wait_for_no_initializing_shards":{ - "type":"boolean", - "description":"Whether to wait until there are no initializing shards in the cluster" - }, - "wait_for_status":{ - "type":"enum", - "options":[ - "green", - "yellow", - "red" - ], - "default":null, - "description":"Wait until cluster is in a specific state" - } - } - } -} diff --git a/packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_autocomplete.json b/packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_autocomplete.json deleted file mode 100644 index 3553bd9873690..0000000000000 --- a/packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_autocomplete.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "snapshot.get": { - "url_params": { - "master_timeout": "", - "ignore_unavailable": "__flag__", - "index_names": "__flag__", - "index_details": "__flag__", - "include_repository": "__flag__", - "sort": [ - "start_time", - "duration", - "name", - "repository", - "index_count", - "shard_count", - "failed_shard_count" - ], - "size": 0, - "order": [ - "asc", - "desc" - ], - "from_sort_value": "", - "after": "", - "offset": 0, - "slm_policy_filter": "", - "verbose": "__flag__" - }, - "methods": [ - "GET" - ], - "patterns": [ - "_snapshot/{repository}/{snapshot}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-snapshots.html" - } -} diff --git a/packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_spec.json b/packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_spec.json deleted file mode 100644 index 23f5f737995d0..0000000000000 --- a/packages/kbn-spec-to-console/lib/__fixtures__/snapshot_get_spec.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "snapshot.get":{ - "documentation":{ - "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-snapshots.html", - "description":"Returns information about a snapshot." - }, - "stability":"stable", - "visibility":"public", - "headers":{ - "accept": [ "application/json"] - }, - "url":{ - "paths":[ - { - "path":"/_snapshot/{repository}/{snapshot}", - "methods":[ - "GET" - ], - "parts":{ - "repository":{ - "type":"string", - "description":"A repository name" - }, - "snapshot":{ - "type":"list", - "description":"A comma-separated list of snapshot names" - } - } - } - ] - }, - "params":{ - "master_timeout":{ - "type":"time", - "description":"Explicit operation timeout for connection to master node" - }, - "ignore_unavailable":{ - "type":"boolean", - "description":"Whether to ignore unavailable snapshots, defaults to false which means a SnapshotMissingException is thrown" - }, - "index_names":{ - "type":"boolean", - "description":"Whether to include the name of each index in the snapshot. Defaults to true." - }, - "index_details":{ - "type":"boolean", - "description":"Whether to include details of each index in the snapshot, if those details are available. Defaults to false." - }, - "include_repository":{ - "type":"boolean", - "description":"Whether to include the repository name in the snapshot info. Defaults to true." - }, - "sort": { - "type": "enum", - "default": "start_time", - "options": ["start_time", "duration", "name", "repository", "index_count", "shard_count", "failed_shard_count"], - "description": "Allows setting a sort order for the result. Defaults to start_time" - }, - "size": { - "type": "integer", - "description": "Maximum number of snapshots to return. Defaults to 0 which means return all that match without limit." - }, - "order": { - "type": "enum", - "default": "asc", - "options": ["asc", "desc"], - "description": "Sort order" - }, - "from_sort_value": { - "type": "string", - "description": "Value of the current sort column at which to start retrieval." - }, - "after": { - "type": "string", - "description": "Offset identifier to start pagination from as returned by the 'next' field in the response body." - }, - "offset": { - "type": "integer", - "description": "Numeric offset to start pagination based on the snapshots matching the request. Defaults to 0" - }, - "slm_policy_filter": { - "type": "string", - "description": "Filter snapshots by a comma-separated list of SLM policy names that snapshots belong to. Accepts wildcards. Use the special pattern '_none' to match snapshots without an SLM policy" - }, - "verbose":{ - "type":"boolean", - "description":"Whether to show verbose snapshot info or only show the basic info found in the repository index blob" - } - } - } -} diff --git a/packages/kbn-spec-to-console/lib/convert.js b/packages/kbn-spec-to-console/lib/convert.js deleted file mode 100644 index 93e96ecb452cb..0000000000000 --- a/packages/kbn-spec-to-console/lib/convert.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const convertParams = require('./convert/params'); -const convertMethods = require('./convert/methods'); -const convertPaths = require('./convert/paths'); -const convertParts = require('./convert/parts'); - -module.exports = (spec) => { - const result = {}; - /** - * TODO: - * Since https://github.com/elastic/elasticsearch/pull/42346 has been merged into ES master - * the JSON doc specification has been updated. We need to update this script to take advantage - * of the added information but it will also require updating console editor autocomplete. - * - * Note: for now we exclude all deprecated patterns from the generated spec to prevent them - * from being used in autocompletion. It would be really nice if we could use this information - * instead of just not including it. - */ - Object.keys(spec).forEach((api) => { - const source = spec[api]; - - if (!source.url) { - return result; - } - - if (source.url.path) { - if (source.url.paths.every((path) => Boolean(path.deprecated))) { - return; - } - } - - const convertedSpec = (result[api] = {}); - if (source.params) { - const urlParams = convertParams(source.params); - if (Object.keys(urlParams).length > 0) { - convertedSpec.url_params = urlParams; - } - } - - const methodSet = new Set(); - let patterns; - const urlComponents = {}; - - if (source.url.paths) { - // We filter out all deprecated url patterns here. - const paths = source.url.paths.filter((path) => !path.deprecated); - patterns = convertPaths(paths); - paths.forEach((pathsObject) => { - pathsObject.methods.forEach((method) => methodSet.add(method)); - if (pathsObject.parts) { - for (const partName of Object.keys(pathsObject.parts)) { - urlComponents[partName] = pathsObject.parts[partName]; - } - } - }); - } - - convertedSpec.methods = convertMethods(Array.from(methodSet)); - convertedSpec.patterns = patterns; - - if (Object.keys(urlComponents).length) { - const components = convertParts(urlComponents); - const hasComponents = - Object.keys(components).filter((c) => { - return Boolean(components[c]); - }).length > 0; - if (hasComponents) { - convertedSpec.url_components = convertParts(urlComponents); - } - } - if (source.documentation && source.documentation.url) { - convertedSpec.documentation = source.documentation.url; - } - }); - - return result; -}; diff --git a/packages/kbn-spec-to-console/lib/convert.test.js b/packages/kbn-spec-to-console/lib/convert.test.js deleted file mode 100644 index 2aa81963c7c2f..0000000000000 --- a/packages/kbn-spec-to-console/lib/convert.test.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const convert = require('./convert'); - -const clusterHealthSpec = require('./__fixtures__/cluster_health_spec.json'); -const clusterHealthAutocomplete = require('./__fixtures__/cluster_health_autocomplete.json'); - -const snapshotGetSpec = require('./__fixtures__/snapshot_get_spec.json'); -const snapshotGetAutocomplete = require('./__fixtures__/snapshot_get_autocomplete.json'); - -test('convert', () => { - expect(convert(clusterHealthSpec)).toEqual(clusterHealthAutocomplete); - expect(convert(snapshotGetSpec)).toEqual(snapshotGetAutocomplete); -}); diff --git a/packages/kbn-spec-to-console/lib/convert/methods.js b/packages/kbn-spec-to-console/lib/convert/methods.js deleted file mode 100644 index d1ebb328afaa7..0000000000000 --- a/packages/kbn-spec-to-console/lib/convert/methods.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -module.exports = (methods) => { - return methods; -}; diff --git a/packages/kbn-spec-to-console/lib/convert/params.js b/packages/kbn-spec-to-console/lib/convert/params.js deleted file mode 100644 index f5f31c89418ce..0000000000000 --- a/packages/kbn-spec-to-console/lib/convert/params.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -module.exports = (params) => { - const result = {}; - Object.keys(params).forEach((param) => { - const { type, description = '', options = [] } = params[param]; - const [, defaultValue] = description.match(/\(default: (.*)\)/) || []; - switch (type) { - case undefined: - // { description: 'TODO: ?' } - break; - case 'int': - case 'integer': - result[param] = 0; - break; - case 'double': - result[param] = 0.0; - break; - case 'enum': - // This is to clean up entries like: "d (Days)". We only want the "d" part. - if (param === 'time') { - result[param] = options.map((option) => option.split(' ')[0]); - } else { - result[param] = options; - } - break; - case 'boolean': - result[param] = '__flag__'; - break; - case 'time': - case 'date': - case 'string': - case 'number': - case 'number|string': - case 'boolean|long': - result[param] = defaultValue || ''; - break; - case 'list': - result[param] = []; - break; - default: - throw new Error(`Unexpected type ${type}`); - } - }); - return result; -}; diff --git a/packages/kbn-spec-to-console/lib/convert/parts.js b/packages/kbn-spec-to-console/lib/convert/parts.js deleted file mode 100644 index 475069cdf0433..0000000000000 --- a/packages/kbn-spec-to-console/lib/convert/parts.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const replacePattern = require('../replace_pattern'); - -module.exports = (parts) => { - const result = {}; - Object.keys(parts).forEach((part) => { - const key = replacePattern(part); - const options = parts[part].options; - if (options && options.length) { - result[key] = options.sort(); - } else { - result[key] = null; - } - }); - return result; -}; diff --git a/packages/kbn-spec-to-console/lib/convert/paths.js b/packages/kbn-spec-to-console/lib/convert/paths.js deleted file mode 100644 index a14d7c72dde49..0000000000000 --- a/packages/kbn-spec-to-console/lib/convert/paths.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const replacePattern = require('../replace_pattern'); - -module.exports = (patterns) => { - return patterns.map((patternObject) => { - return replacePattern(patternObject.path); - }); -}; diff --git a/packages/kbn-spec-to-console/lib/replace_pattern.js b/packages/kbn-spec-to-console/lib/replace_pattern.js deleted file mode 100644 index aa687aaa2a481..0000000000000 --- a/packages/kbn-spec-to-console/lib/replace_pattern.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -module.exports = (pattern) => { - return pattern.replace(/^\//, ''); -}; diff --git a/packages/kbn-spec-to-console/package.json b/packages/kbn-spec-to-console/package.json deleted file mode 100644 index d27b3b4168ee1..0000000000000 --- a/packages/kbn-spec-to-console/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@kbn/spec-to-console", - "version": "1.0.0", - "description": "ES REST spec -> Console autocomplete", - "main": "index.js", - "directories": { - "lib": "lib" - }, - "private": true, - "scripts": { - "format": "../../node_modules/.bin/prettier **/*.js --write" - }, - "author": "", - "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", - "bugs": { - "url": "https://github.com/jbudz/spec-to-console/issues" - }, - "homepage": "https://github.com/jbudz/spec-to-console#readme" -} \ No newline at end of file diff --git a/scripts/spec_to_console.js b/scripts/spec_to_console.js deleted file mode 100644 index 11fb2d7f2db2b..0000000000000 --- a/scripts/spec_to_console.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -require('@kbn/spec-to-console/bin/spec_to_console'); diff --git a/tsconfig.base.json b/tsconfig.base.json index dbd9b7b8b1e56..d1ce9880e4a66 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1790,8 +1790,6 @@ "@kbn/spaces-plugin/*": ["x-pack/plugins/spaces/*"], "@kbn/spaces-test-plugin": ["x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin"], "@kbn/spaces-test-plugin/*": ["x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin/*"], - "@kbn/spec-to-console": ["packages/kbn-spec-to-console"], - "@kbn/spec-to-console/*": ["packages/kbn-spec-to-console/*"], "@kbn/sse-utils": ["packages/kbn-sse-utils"], "@kbn/sse-utils/*": ["packages/kbn-sse-utils/*"], "@kbn/sse-utils-client": ["packages/kbn-sse-utils-client"], diff --git a/yarn.lock b/yarn.lock index e9844384de3c9..11778ed7abcc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6875,10 +6875,6 @@ version "0.0.0" uid "" -"@kbn/spec-to-console@link:packages/kbn-spec-to-console": - version "0.0.0" - uid "" - "@kbn/sse-utils-client@link:packages/kbn-sse-utils-client": version "0.0.0" uid "" From ded274062ca55e78a79973704c2c0e8814516c2b Mon Sep 17 00:00:00 2001 From: Sonia Sanz Vivas <sonia.sanzvivas@elastic.co> Date: Tue, 15 Oct 2024 17:11:23 +0200 Subject: [PATCH 39/84] Display error banner instead of warning (#195913) Closes [110155](https://github.com/elastic/kibana/issues/110155) ## Summary When the remote cluster creation has an error the banner displayed was a warning instead of an error. ### How to reproduce? 1.) Navigate to Remote Clusters 2.) Create a new Remote Cluster 3.) Add information for a cluster except set the max number of connections to 999999999999999999 4.) Try to save. ### Testing No testing since this doesn't change any functionality. <img width="1243" alt="110155" src="https://github.com/user-attachments/assets/802aa9f3-f70a-44da-a135-8187ada2e78a"> --- .../components/remote_cluster_form/remote_cluster_form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx index 07cbe6b58340f..083f7b8f06c93 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx @@ -423,7 +423,7 @@ export class RemoteClusterForm extends Component<Props, State> { return ( <Fragment> - <EuiCallOut title={message} iconType="cross" color="warning"> + <EuiCallOut title={message} color="danger" iconType="error"> {errorBody} </EuiCallOut> From 489dc1dca3dc7793ebbf147e698834b9e54e3d7f Mon Sep 17 00:00:00 2001 From: Ido Cohen <90558359+CohenIdo@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:16:15 +0300 Subject: [PATCH 40/84] CDR workflow UI counters --- .../common/utils/ui_metrics.ts | 19 +++++++++++++------ ...isconfiguration_findings_details_table.tsx | 12 +++++++++--- ...vulnerabilities_findings_details_table.tsx | 12 +++++++++--- .../misconfiguration_preview.tsx | 10 +++++++++- .../vulnerabilities_preview.tsx | 11 ++++++++++- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts index 8ecedd744efef..252252b08e976 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts @@ -10,8 +10,14 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; export const APP_NAME = 'cloud-security'; -export const ENTITY_FLYOUT_MISCONFIGURATION_VIEW_VISITS = - 'entity-flyout-misconfiguration-view-visits'; +export const ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT = + 'entity-flyout-with-misconfiguration-visits'; +export const ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW = + 'entity-flyout-with-vulnerability-preview-visits'; +export const ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS = + 'entity-flyout-expand-misconfiguration-view-visits'; +export const ENTITY_FLYOUT_EXPAND_VULNERABILITY_VIEW_VISITS = + 'entity-flyout-expand-vulnerability-view-visits'; export const NAV_TO_FINDINGS_BY_HOST_NAME_FRPOM_ENTITY_FLYOUT = 'nav-to-findings-by-host-name-from-entity-flyout'; export const NAV_TO_FINDINGS_BY_RULE_NAME_FRPOM_ENTITY_FLYOUT = @@ -22,18 +28,19 @@ export const VULNERABILITIES_FLYOUT_VISITS = 'vulnerabilities-flyout-visits'; export const OPEN_FINDINGS_FLYOUT = 'open-findings-flyout'; export const GROUP_BY_CLICK = 'group-by-click'; export const CHANGE_RULE_STATE = 'change-rule-state'; -export const ENTITY_FLYOUT_VULNERABILITY_VIEW_VISITS = 'entity-flyout-vulnerability-view-visits'; type CloudSecurityUiCounters = - | typeof ENTITY_FLYOUT_MISCONFIGURATION_VIEW_VISITS + | typeof ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT + | typeof ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW + | typeof ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS + | typeof ENTITY_FLYOUT_EXPAND_VULNERABILITY_VIEW_VISITS | typeof NAV_TO_FINDINGS_BY_HOST_NAME_FRPOM_ENTITY_FLYOUT - | typeof VULNERABILITIES_FLYOUT_VISITS | typeof NAV_TO_FINDINGS_BY_RULE_NAME_FRPOM_ENTITY_FLYOUT + | typeof VULNERABILITIES_FLYOUT_VISITS | typeof OPEN_FINDINGS_FLYOUT | typeof CREATE_DETECTION_RULE_FROM_FLYOUT | typeof CREATE_DETECTION_FROM_TABLE_ROW_ACTION | typeof GROUP_BY_CLICK - | typeof ENTITY_FLYOUT_VULNERABILITY_VIEW_VISITS | typeof CHANGE_RULE_STATE; export class UiMetricService { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 2cf99abdf4833..81547f7bbf5ac 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useState } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; import { EuiSpacer, EuiIcon, EuiPanel, EuiLink, EuiText, EuiBasicTable } from '@elastic/eui'; import { useMisconfigurationFindings } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; @@ -18,7 +18,7 @@ import { useNavigateFindings } from '@kbn/cloud-security-posture/src/hooks/use_n import type { CspBenchmarkRuleMetadata } from '@kbn/cloud-security-posture-common/schema/rules/latest'; import { CspEvaluationBadge } from '@kbn/cloud-security-posture'; import { - ENTITY_FLYOUT_MISCONFIGURATION_VIEW_VISITS, + ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS, NAV_TO_FINDINGS_BY_HOST_NAME_FRPOM_ENTITY_FLYOUT, NAV_TO_FINDINGS_BY_RULE_NAME_FRPOM_ENTITY_FLYOUT, uiMetricService, @@ -58,7 +58,13 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb */ export const MisconfigurationFindingsDetailsTable = memo( ({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => { - uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, ENTITY_FLYOUT_MISCONFIGURATION_VIEW_VISITS); + useEffect(() => { + uiMetricService.trackUiMetric( + METRIC_TYPE.COUNT, + ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS + ); + }, []); + const { data } = useMisconfigurationFindings({ query: buildEntityFlyoutPreviewQuery(fieldName, queryName), sort: [], diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx index 9e3e4b140a9ba..d004ffe45d5dd 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useState } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; import { EuiSpacer, EuiIcon, EuiPanel, EuiLink, EuiText, EuiBasicTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -24,7 +24,7 @@ import { SeverityStatusBadge, } from '@kbn/cloud-security-posture'; import { - ENTITY_FLYOUT_VULNERABILITY_VIEW_VISITS, + ENTITY_FLYOUT_EXPAND_VULNERABILITY_VIEW_VISITS, NAV_TO_FINDINGS_BY_HOST_NAME_FRPOM_ENTITY_FLYOUT, uiMetricService, } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; @@ -42,7 +42,13 @@ interface VulnerabilitiesPackage extends Vulnerability { } export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryName: string }) => { - uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, ENTITY_FLYOUT_VULNERABILITY_VIEW_VISITS); + useEffect(() => { + uiMetricService.trackUiMetric( + METRIC_TYPE.COUNT, + ENTITY_FLYOUT_EXPAND_VULNERABILITY_VIEW_VISITS + ); + }, []); + const { data } = useVulnerabilitiesFindings({ query: buildEntityFlyoutPreviewQuery('host.name', queryName), sort: [], diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index a372ca4755fd8..686ee93c260f7 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { css } from '@emotion/react'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, useEuiTheme, EuiTitle } from '@elastic/eui'; @@ -19,6 +19,11 @@ import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-commo import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { + ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT, + uiMetricService, +} from '@kbn/cloud-security-posture-common/utils/ui_metrics'; import { CspInsightLeftPanelSubTab, EntityDetailsLeftPanelTab, @@ -120,6 +125,9 @@ export const MisconfigurationsPreview = ({ const passedFindings = data?.count.passed || 0; const failedFindings = data?.count.failed || 0; + useEffect(() => { + uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT); + }, []); const { euiTheme } = useEuiTheme(); const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx index eef778b1e6f0c..216ca41fc0fed 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { css } from '@emotion/react'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, useEuiTheme, EuiTitle } from '@elastic/eui'; @@ -20,6 +20,11 @@ import { import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { + ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW, + uiMetricService, +} from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { METRIC_TYPE } from '@kbn/analytics'; import { EntityDetailsLeftPanelTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; @@ -71,6 +76,10 @@ export const VulnerabilitiesPreview = ({ name: string; isPreviewMode?: boolean; }) => { + useEffect(() => { + uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW); + }, []); + const { data } = useVulnerabilitiesPreview({ query: buildEntityFlyoutPreviewQuery('host.name', name), sort: [], From 1bc487c1bf49d49d7d1573c20f9ff5be375c0c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= <mikecote@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:21:09 -0400 Subject: [PATCH 41/84] Set MGet as the claim strategy for serverless (#194694) In this PR, I'm modifying the `config/serverless.yml` file to contain `xpack.task_manager.claim_strategy: mget`. We've rolled out the mget task claimer in phases using the kibana-controller, now that all projects are using the mget task claiming strategy, we can move the config here and cleanup all the places in the kibana-controller that set this flag. Once this commit rolls out to all serverless projects, I'll be able to start cleaning up the kibana-controller. Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- config/serverless.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/serverless.yml b/config/serverless.yml index d06b4e829e747..8f7857988d77e 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -204,6 +204,7 @@ uiSettings: labs:dashboard:deferBelowFold: false # Task Manager +xpack.task_manager.claim_strategy: mget xpack.task_manager.allow_reading_invalid_state: false xpack.task_manager.request_timeouts.update_by_query: 60000 xpack.task_manager.metrics_reset_interval: 120000 From fe22ac99281c9750e9dd55b16fc3ca284ba7683c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:21:32 +0100 Subject: [PATCH 42/84] [Synthtrace] Adding Entities support (#196258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## known issue ``` - Transforms are not started by synthtrace. Because it duplicates data ingested by synthrace on signal indices. And it takes a long time to generate data. - We are not able to open the Inventory page because of 👆🏻. ``` --- ``` node scripts/synthtrace.js traces_logs_entities.ts --clean --live ``` or ``` node scripts/synthtrace.js traces_logs_entities.ts --clean --from=2024-04-08T08:00:00.000Z --to=2024-04-08T08:15:00.000Z ``` docs produces by the new scenario: ``` { "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 1, "hits": [ { "_index": ".entities.v1.latest.builtin_services_from_ecs_data", "_id": "2846700000000001", "_score": 1, "_source": { "service": { "name": "synth-node-trace-logs", "environment": "Synthtrace: traces_logs_entities" }, "source_data_stream": { "type": [ "traces", "logs" ] }, "agent": { "name": [ "nodejs" ] }, "entity": { "id": "2846700000000001", "type": "service", "definitionId": "latest", "lastSeenTimestamp": "2024-10-15T08:56:20.562Z" }, "event": { "ingested": "2024-10-15T08:56:20.562Z" } } }, { "_index": ".entities.v1.latest.builtin_services_from_ecs_data", "_id": "2846700000000000", "_score": 1, "_source": { "service": { "name": "synth-java-trace", "environment": "Synthtrace: traces_logs_entities" }, "source_data_stream": { "type": [ "traces" ] }, "agent": { "name": [ "java" ] }, "entity": { "id": "2846700000000000", "type": "service", "definitionId": "latest", "lastSeenTimestamp": "2024-10-15T08:56:20.562Z" }, "event": { "ingested": "2024-10-15T08:56:20.562Z" } } }, { "_index": ".entities.v1.latest.builtin_services_from_ecs_data", "_id": "2846700000000002", "_score": 1, "_source": { "service": { "name": "synth-go-logs", "environment": "Synthtrace: traces_logs_entities" }, "source_data_stream": { "type": [ "logs" ] }, "agent": { "name": [ "go" ] }, "entity": { "id": "2846700000000002", "type": "service", "definitionId": "latest", "lastSeenTimestamp": "2024-10-15T08:56:20.562Z" }, "event": { "ingested": "2024-10-15T08:56:20.562Z" } } } ] } } ``` --- packages/kbn-apm-synthtrace-client/index.ts | 2 +- .../src/lib/assets/asset.ts | 27 --- .../src/lib/assets/index.ts | 12 -- .../src/lib/assets/service_assets.ts | 23 --- .../src/lib/entities/container_entity.ts | 43 +++++ .../src/lib/entities/host_entity.ts | 43 +++++ .../src/lib/entities/index.ts | 35 ++++ .../src/lib/entities/service_entity.ts | 43 +++++ packages/kbn-apm-synthtrace/index.ts | 2 +- .../kbn-apm-synthtrace/src/cli/scenario.ts | 11 +- .../src/cli/utils/bootstrap.ts | 15 +- .../utils/get_entites_kibana_client.ts} | 13 +- ...es_client.ts => get_entities_es_client.ts} | 6 +- .../cli/utils/start_historical_data_upload.ts | 3 +- .../src/cli/utils/start_live_data_upload.ts | 19 ++- .../src/cli/utils/synthtrace_worker.ts | 32 ++-- .../entities_synthtrace_kibana_client.ts | 62 +++++++ .../create_logs_service_assets_aggregator.ts | 42 ----- .../create_traces_assets_aggregator.ts | 13 -- ...create_traces_service_assets_aggregator.ts | 45 ----- .../lib/assets/assets_synthtrace_es_client.ts | 116 ------------- .../entities/entities_synthtrace_es_client.ts | 82 +++++++++ .../src/lib/shared/base_client.ts | 10 +- .../utils/create_assets_aggregator_factory.ts | 94 ----------- ...logs_assets.ts => traces_logs_entities.ts} | 156 ++++++++++-------- .../test/apm_api_integration/common/config.ts | 10 +- 26 files changed, 474 insertions(+), 485 deletions(-) delete mode 100644 packages/kbn-apm-synthtrace-client/src/lib/assets/asset.ts delete mode 100644 packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts delete mode 100644 packages/kbn-apm-synthtrace-client/src/lib/assets/service_assets.ts create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.ts create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.ts create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.ts rename packages/kbn-apm-synthtrace/src/{lib/assets/aggregators/create_logs_assets_aggregator.ts => cli/utils/get_entites_kibana_client.ts} (55%) rename packages/kbn-apm-synthtrace/src/cli/utils/{get_assets_es_client.ts => get_entities_es_client.ts} (84%) create mode 100644 packages/kbn-apm-synthtrace/src/lib/apm/client/entities_synthtrace_kibana_client.ts delete mode 100644 packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_service_assets_aggregator.ts delete mode 100644 packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_assets_aggregator.ts delete mode 100644 packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_service_assets_aggregator.ts delete mode 100644 packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts create mode 100644 packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts delete mode 100644 packages/kbn-apm-synthtrace/src/lib/utils/create_assets_aggregator_factory.ts rename packages/kbn-apm-synthtrace/src/scenarios/{traces_logs_assets.ts => traces_logs_entities.ts} (63%) diff --git a/packages/kbn-apm-synthtrace-client/index.ts b/packages/kbn-apm-synthtrace-client/index.ts index d3d24a8940a3b..ff343ab78ab46 100644 --- a/packages/kbn-apm-synthtrace-client/index.ts +++ b/packages/kbn-apm-synthtrace-client/index.ts @@ -35,6 +35,6 @@ export { generateLongId, generateShortId } from './src/lib/utils/generate_id'; export { appendHash, hashKeysOf } from './src/lib/utils/hash'; export type { ESDocumentWithOperation, SynthtraceESAction, SynthtraceGenerator } from './src/types'; export { log, type LogDocument, LONG_FIELD_NAME } from './src/lib/logs'; -export { type AssetDocument } from './src/lib/assets'; export { syntheticsMonitor, type SyntheticsMonitorDocument } from './src/lib/synthetics'; export { otel, type OtelDocument } from './src/lib/otel'; +export { type EntityFields, entities } from './src/lib/entities'; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/assets/asset.ts b/packages/kbn-apm-synthtrace-client/src/lib/assets/asset.ts deleted file mode 100644 index f5968fff23e30..0000000000000 --- a/packages/kbn-apm-synthtrace-client/src/lib/assets/asset.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { Fields } from '../entity'; -import { Serializable } from '../serializable'; - -type AssetType = 'host' | 'pod' | 'container' | 'service' | 'aws_rds'; - -export interface AssetDocument extends Fields { - 'asset.id': string; - 'asset.type': AssetType; - 'asset.first_seen': string; - 'asset.last_seen': string; - 'asset.identifying_metadata': string[]; - 'asset.signalTypes': { - 'asset.traces'?: boolean; - 'asset.logs'?: boolean; - }; -} - -export class Asset<F extends AssetDocument> extends Serializable<F> {} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts deleted file mode 100644 index 2704d210b0796..0000000000000 --- a/packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { ServiceAssetDocument } from './service_assets'; - -export type AssetDocument = ServiceAssetDocument; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/assets/service_assets.ts b/packages/kbn-apm-synthtrace-client/src/lib/assets/service_assets.ts deleted file mode 100644 index c3ae21bf6bf4b..0000000000000 --- a/packages/kbn-apm-synthtrace-client/src/lib/assets/service_assets.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { Asset, AssetDocument } from './asset'; - -export interface ServiceAssetDocument extends AssetDocument { - 'service.language.name'?: string; - 'service.name': string; - 'service.node.name'?: string; - 'service.environment'?: string; -} - -export class ServiceAsset extends Asset<ServiceAssetDocument> { - constructor(fields: Omit<ServiceAssetDocument, 'asset.type'>) { - super({ 'asset.type': 'service', ...fields }); - } -} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.ts new file mode 100644 index 0000000000000..6f9dfb4aabca8 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityDataStreamType, EntityFields } from '.'; +import { Serializable } from '../serializable'; + +class ContainerEntity extends Serializable<EntityFields> { + constructor(fields: EntityFields) { + super({ + ...fields, + 'entity.type': 'container', + 'entity.definitionId': 'latest', + }); + } +} + +export function containerEntity({ + agentName, + dataStreamType, + dataStreamDataset, + containerId, + entityId, +}: { + agentName: string[]; + dataStreamType: EntityDataStreamType[]; + dataStreamDataset: string; + containerId: string; + entityId: string; +}) { + return new ContainerEntity({ + 'source_data_stream.type': dataStreamType, + 'source_data_stream.dataset': dataStreamDataset, + 'agent.name': agentName, + 'container.id': containerId, + 'entity.id': entityId, + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.ts new file mode 100644 index 0000000000000..47ffdd67dcbd7 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityDataStreamType, EntityFields } from '.'; +import { Serializable } from '../serializable'; + +class HostEntity extends Serializable<EntityFields> { + constructor(fields: EntityFields) { + super({ + ...fields, + 'entity.type': 'host', + 'entity.definitionId': 'latest', + }); + } +} + +export function hostEntity({ + agentName, + dataStreamType, + dataStreamDataset, + hostName, + entityId, +}: { + agentName: string[]; + dataStreamType: EntityDataStreamType[]; + dataStreamDataset: string; + hostName: string; + entityId: string; +}) { + return new HostEntity({ + 'source_data_stream.type': dataStreamType, + 'source_data_stream.dataset': dataStreamDataset, + 'agent.name': agentName, + 'host.name': hostName, + 'entity.id': entityId, + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts new file mode 100644 index 0000000000000..10cf982ff41ee --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Fields } from '../entity'; +import { serviceEntity } from './service_entity'; +import { hostEntity } from './host_entity'; +import { containerEntity } from './container_entity'; + +export type EntityDataStreamType = 'metrics' | 'logs' | 'traces'; + +export type EntityFields = Fields & + Partial<{ + 'agent.name': string[]; + 'source_data_stream.type': string | string[]; + 'source_data_stream.dataset': string | string[]; + 'event.ingested': string; + sourceIndex: string; + 'entity.lastSeenTimestamp': string; + 'entity.schemaVersion': string; + 'entity.definitionVersion': string; + 'entity.displayName': string; + 'entity.identityFields': string | string[]; + 'entity.id': string; + 'entity.type': string; + 'entity.definitionId': string; + [key: string]: any; + }>; + +export const entities = { serviceEntity, hostEntity, containerEntity }; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.ts new file mode 100644 index 0000000000000..2d304ecd21b92 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityDataStreamType, EntityFields } from '.'; +import { Serializable } from '../serializable'; + +class ServiceEntity extends Serializable<EntityFields> { + constructor(fields: EntityFields) { + super({ + ...fields, + 'entity.type': 'service', + 'entity.definitionId': 'latest', + }); + } +} + +export function serviceEntity({ + agentName, + dataStreamType, + serviceName, + environment, + entityId, +}: { + agentName: string[]; + serviceName: string; + dataStreamType: EntityDataStreamType[]; + environment?: string; + entityId: string; +}) { + return new ServiceEntity({ + 'service.name': serviceName, + 'service.environment': environment, + 'source_data_stream.type': dataStreamType, + 'agent.name': agentName, + 'entity.id': entityId, + }); +} diff --git a/packages/kbn-apm-synthtrace/index.ts b/packages/kbn-apm-synthtrace/index.ts index ebd35da3aa19e..1eaab89a89308 100644 --- a/packages/kbn-apm-synthtrace/index.ts +++ b/packages/kbn-apm-synthtrace/index.ts @@ -15,7 +15,7 @@ export { InfraSynthtraceEsClient } from './src/lib/infra/infra_synthtrace_es_cli export { InfraSynthtraceKibanaClient } from './src/lib/infra/infra_synthtrace_kibana_client'; export { MonitoringSynthtraceEsClient } from './src/lib/monitoring/monitoring_synthtrace_es_client'; export { LogsSynthtraceEsClient } from './src/lib/logs/logs_synthtrace_es_client'; -export { AssetsSynthtraceEsClient } from './src/lib/assets/assets_synthtrace_es_client'; +export { EntitiesSynthtraceEsClient } from './src/lib/entities/entities_synthtrace_es_client'; export { SyntheticsSynthtraceEsClient } from './src/lib/synthetics/synthetics_synthtrace_es_client'; export { OtelSynthtraceEsClient } from './src/lib/otel/otel_synthtrace_es_client'; export { diff --git a/packages/kbn-apm-synthtrace/src/cli/scenario.ts b/packages/kbn-apm-synthtrace/src/cli/scenario.ts index 4f1550b8bdbc8..09bed89648f8b 100644 --- a/packages/kbn-apm-synthtrace/src/cli/scenario.ts +++ b/packages/kbn-apm-synthtrace/src/cli/scenario.ts @@ -14,19 +14,24 @@ import { LogsSynthtraceEsClient, SyntheticsSynthtraceEsClient, OtelSynthtraceEsClient, + EntitiesSynthtraceEsClient, } from '../..'; -import { AssetsSynthtraceEsClient } from '../lib/assets/assets_synthtrace_es_client'; import { Logger } from '../lib/utils/create_logger'; import { ScenarioReturnType } from '../lib/utils/with_client'; import { RunOptions } from './utils/parse_run_cli_flags'; +import { EntitiesSynthtraceKibanaClient } from '../lib/apm/client/entities_synthtrace_kibana_client'; interface EsClients { apmEsClient: ApmSynthtraceEsClient; logsEsClient: LogsSynthtraceEsClient; infraEsClient: InfraSynthtraceEsClient; - assetsEsClient: AssetsSynthtraceEsClient; syntheticsEsClient: SyntheticsSynthtraceEsClient; otelEsClient: OtelSynthtraceEsClient; + entitiesEsClient: EntitiesSynthtraceEsClient; +} + +interface KibanaClients { + entitiesKibanaClient: EntitiesSynthtraceKibanaClient; } type Generate<TFields> = (options: { @@ -35,6 +40,6 @@ type Generate<TFields> = (options: { }) => ScenarioReturnType<TFields> | Array<ScenarioReturnType<TFields>>; export type Scenario<TFields> = (options: RunOptions & { logger: Logger }) => Promise<{ - bootstrap?: (options: EsClients) => Promise<void>; + bootstrap?: (options: EsClients & KibanaClients) => Promise<void>; generate: Generate<TFields>; }>; diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts b/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts index 22d07f73c56cb..a305e4354c145 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts @@ -14,9 +14,10 @@ import { getInfraEsClient } from './get_infra_es_client'; import { getKibanaClient } from './get_kibana_client'; import { getServiceUrls } from './get_service_urls'; import { RunOptions } from './parse_run_cli_flags'; -import { getAssetsEsClient } from './get_assets_es_client'; import { getSyntheticsEsClient } from './get_synthetics_es_client'; import { getOtelSynthtraceEsClient } from './get_otel_es_client'; +import { getEntitiesEsClient } from './get_entities_es_client'; +import { getEntitiesKibanaClient } from './get_entites_kibana_client'; export async function bootstrap(runOptions: RunOptions) { const logger = createLogger(runOptions.logLevel); @@ -58,12 +59,17 @@ export async function bootstrap(runOptions: RunOptions) { concurrency: runOptions.concurrency, }); - const assetsEsClient = getAssetsEsClient({ + const entitiesEsClient = getEntitiesEsClient({ target: esUrl, logger, concurrency: runOptions.concurrency, }); + const entitiesKibanaClient = getEntitiesKibanaClient({ + target: kibanaUrl, + logger, + }); + const syntheticsEsClient = getSyntheticsEsClient({ target: esUrl, logger, @@ -79,7 +85,7 @@ export async function bootstrap(runOptions: RunOptions) { await apmEsClient.clean(); await logsEsClient.clean(); await infraEsClient.clean(); - await assetsEsClient.clean(); + await entitiesEsClient.clean(); await syntheticsEsClient.clean(); await otelEsClient.clean(); } @@ -89,11 +95,12 @@ export async function bootstrap(runOptions: RunOptions) { apmEsClient, logsEsClient, infraEsClient, - assetsEsClient, + entitiesEsClient, syntheticsEsClient, otelEsClient, version, kibanaUrl, esUrl, + entitiesKibanaClient, }; } diff --git a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_assets_aggregator.ts b/packages/kbn-apm-synthtrace/src/cli/utils/get_entites_kibana_client.ts similarity index 55% rename from packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_assets_aggregator.ts rename to packages/kbn-apm-synthtrace/src/cli/utils/get_entites_kibana_client.ts index 3dc71a6e9aec5..e89a4beaf3a00 100644 --- a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_assets_aggregator.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/get_entites_kibana_client.ts @@ -7,7 +7,14 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { LogDocument } from '@kbn/apm-synthtrace-client'; -import { createAssetsAggregatorFactory } from '../../utils/create_assets_aggregator_factory'; +import { EntitiesSynthtraceKibanaClient } from '../../lib/apm/client/entities_synthtrace_kibana_client'; +import { Logger } from '../../lib/utils/create_logger'; -export const createLogsAssetsAggregator = createAssetsAggregatorFactory<LogDocument>(); +export function getEntitiesKibanaClient({ target, logger }: { target: string; logger: Logger }) { + const kibanaClient = new EntitiesSynthtraceKibanaClient({ + logger, + target, + }); + + return kibanaClient; +} diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/get_assets_es_client.ts b/packages/kbn-apm-synthtrace/src/cli/utils/get_entities_es_client.ts similarity index 84% rename from packages/kbn-apm-synthtrace/src/cli/utils/get_assets_es_client.ts rename to packages/kbn-apm-synthtrace/src/cli/utils/get_entities_es_client.ts index 9f30e40fab73f..b52908b470551 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/get_assets_es_client.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/get_entities_es_client.ts @@ -8,12 +8,12 @@ */ import { Client } from '@elastic/elasticsearch'; -import { AssetsSynthtraceEsClient } from '../../lib/assets/assets_synthtrace_es_client'; +import { EntitiesSynthtraceEsClient } from '../../lib/entities/entities_synthtrace_es_client'; import { Logger } from '../../lib/utils/create_logger'; import { RunOptions } from './parse_run_cli_flags'; import { getEsClientTlsSettings } from './ssl'; -export function getAssetsEsClient({ +export function getEntitiesEsClient({ target, logger, concurrency, @@ -26,7 +26,7 @@ export function getAssetsEsClient({ tls: getEsClientTlsSettings(target), }); - return new AssetsSynthtraceEsClient({ + return new EntitiesSynthtraceEsClient({ client, logger, concurrency, diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/start_historical_data_upload.ts b/packages/kbn-apm-synthtrace/src/cli/utils/start_historical_data_upload.ts index 433f58041ef28..0f0d20c6865aa 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/start_historical_data_upload.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/start_historical_data_upload.ts @@ -26,7 +26,7 @@ export async function startHistoricalDataUpload({ from: Date; to: Date; }) { - const { logger, esUrl, version } = await bootstrap(runOptions); + const { logger, esUrl, version, kibanaUrl } = await bootstrap(runOptions); const cores = cpus().length; @@ -93,6 +93,7 @@ export async function startHistoricalDataUpload({ workerId: workerIndex.toString(), esUrl, version, + kibanaUrl, }; const worker = new Worker(Path.join(__dirname, './worker.js'), { workerData, diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts b/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts index 79c9907dc13d1..38404be151612 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts @@ -31,13 +31,26 @@ export async function startLiveDataUpload({ apmEsClient, logsEsClient, infraEsClient, - assetsEsClient, syntheticsEsClient, otelEsClient, + entitiesEsClient, + entitiesKibanaClient, } = await bootstrap(runOptions); const scenario = await getScenario({ file, logger }); - const { generate } = await scenario({ ...runOptions, logger }); + const { generate, bootstrap: scenarioBootsrap } = await scenario({ ...runOptions, logger }); + + if (scenarioBootsrap) { + await scenarioBootsrap({ + apmEsClient, + logsEsClient, + infraEsClient, + otelEsClient, + syntheticsEsClient, + entitiesEsClient, + entitiesKibanaClient, + }); + } const bucketSizeInMs = 1000 * 60; let requestedUntil = start; @@ -76,7 +89,7 @@ export async function startLiveDataUpload({ logsEsClient, apmEsClient, infraEsClient, - assetsEsClient, + entitiesEsClient, syntheticsEsClient, otelEsClient, }, diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts b/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts index 78c89d110c892..72644bd8f1103 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/synthtrace_worker.ts @@ -7,20 +7,21 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { parentPort, workerData } from 'worker_threads'; -import pidusage from 'pidusage'; +import { timerange } from '@kbn/apm-synthtrace-client'; import { castArray } from 'lodash'; +import pidusage from 'pidusage'; import { memoryUsage } from 'process'; -import { timerange } from '@kbn/apm-synthtrace-client'; +import { parentPort, workerData } from 'worker_threads'; import { getApmEsClient } from './get_apm_es_client'; +import { getEntitiesKibanaClient } from './get_entites_kibana_client'; +import { getEntitiesEsClient } from './get_entities_es_client'; +import { getInfraEsClient } from './get_infra_es_client'; +import { getLogsEsClient } from './get_logs_es_client'; +import { getOtelSynthtraceEsClient } from './get_otel_es_client'; import { getScenario } from './get_scenario'; +import { getSyntheticsEsClient } from './get_synthetics_es_client'; import { loggerProxy } from './logger_proxy'; import { RunOptions } from './parse_run_cli_flags'; -import { getLogsEsClient } from './get_logs_es_client'; -import { getInfraEsClient } from './get_infra_es_client'; -import { getAssetsEsClient } from './get_assets_es_client'; -import { getSyntheticsEsClient } from './get_synthetics_es_client'; -import { getOtelSynthtraceEsClient } from './get_otel_es_client'; export interface WorkerData { bucketFrom: Date; @@ -29,18 +30,24 @@ export interface WorkerData { workerId: string; esUrl: string; version: string; + kibanaUrl: string; } -const { bucketFrom, bucketTo, runOptions, esUrl, version } = workerData as WorkerData; +const { bucketFrom, bucketTo, runOptions, esUrl, version, kibanaUrl } = workerData as WorkerData; async function start() { const logger = loggerProxy; - const assetsEsClient = getAssetsEsClient({ + const entitiesEsClient = getEntitiesEsClient({ concurrency: runOptions.concurrency, target: esUrl, logger, }); + const entitiesKibanaClient = getEntitiesKibanaClient({ + target: kibanaUrl, + logger, + }); + const apmEsClient = getApmEsClient({ concurrency: runOptions.concurrency, target: esUrl, @@ -85,9 +92,10 @@ async function start() { apmEsClient, logsEsClient, infraEsClient, - assetsEsClient, syntheticsEsClient, otelEsClient, + entitiesEsClient, + entitiesKibanaClient, }); } @@ -100,7 +108,7 @@ async function start() { logsEsClient, apmEsClient, infraEsClient, - assetsEsClient, + entitiesEsClient, syntheticsEsClient, otelEsClient, }, diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/entities_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/entities_synthtrace_kibana_client.ts new file mode 100644 index 0000000000000..358a66570c9bd --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/entities_synthtrace_kibana_client.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import fetch from 'node-fetch'; +import { Logger } from '../../utils/create_logger'; +import { kibanaHeaders } from '../../shared/client_headers'; +import { getFetchAgent } from '../../../cli/utils/ssl'; + +interface EntityDefinitionResponse { + definitions: Array<{ type: string; state: { installed: boolean; running: boolean } }>; +} + +export class EntitiesSynthtraceKibanaClient { + private readonly logger: Logger; + private target: string; + + constructor(options: { logger: Logger; target: string }) { + this.logger = options.logger; + this.target = options.target; + } + + async installEntityIndexPatterns() { + const url = `${this.target}/internal/entities/definition?includeState=true`; + const response = await fetch(url, { + method: 'GET', + headers: kibanaHeaders(), + agent: getFetchAgent(url), + }); + const entityDefinition: EntityDefinitionResponse = await response.json(); + + const hasEntityDefinitionsInstalled = entityDefinition.definitions.find( + (definition) => definition.type === 'service' + )?.state.installed; + + if (hasEntityDefinitionsInstalled === true) { + this.logger.debug('Entity definitions are already defined'); + } else { + this.logger.debug('Installing Entity definitions'); + const entityEnablementUrl = `${this.target}/internal/entities/managed/enablement?installOnly=true`; + await fetch(entityEnablementUrl, { + method: 'PUT', + headers: kibanaHeaders(), + agent: getFetchAgent(url), + }); + } + } + + async uninstallEntityIndexPatterns() { + const url = `${this.target}/internal/entities/managed/enablement`; + await fetch(url, { + method: 'DELETE', + headers: kibanaHeaders(), + agent: getFetchAgent(url), + }); + } +} diff --git a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_service_assets_aggregator.ts b/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_service_assets_aggregator.ts deleted file mode 100644 index 71ece2d4367de..0000000000000 --- a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_logs_service_assets_aggregator.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { hashKeysOf, LogDocument } from '@kbn/apm-synthtrace-client'; -import { ServiceAssetDocument } from '@kbn/apm-synthtrace-client/src/lib/assets/service_assets'; -import { identity, noop } from 'lodash'; -import { createLogsAssetsAggregator } from './create_logs_assets_aggregator'; - -const KEY_FIELDS: Array<keyof LogDocument> = ['service.name']; - -export function createLogsServiceAssetsAggregator() { - return createLogsAssetsAggregator<ServiceAssetDocument>( - { - filter: (event) => event['input.type'] === 'logs', - getAggregateKey: (event) => { - // see https://github.com/elastic/apm-server/blob/main/x-pack/apm-server/aggregation/txmetrics/aggregator.go - return hashKeysOf(event as LogDocument, KEY_FIELDS as Array<keyof LogDocument>); - }, - init: (event, firstSeen, lastSeen) => { - return { - 'asset.id': event['service.name']!, - 'asset.type': 'service', - 'asset.identifying_metadata': ['service.name'], - 'asset.first_seen': firstSeen, - 'asset.last_seen': lastSeen, - 'asset.signalTypes': { - 'asset.logs': true, - }, - 'service.name': event['service.name']!, - }; - }, - }, - noop, - identity - ); -} diff --git a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_assets_aggregator.ts b/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_assets_aggregator.ts deleted file mode 100644 index dd173b97785ef..0000000000000 --- a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_assets_aggregator.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { ApmFields } from '@kbn/apm-synthtrace-client'; -import { createAssetsAggregatorFactory } from '../../utils/create_assets_aggregator_factory'; - -export const createTracesAssetsAggregator = createAssetsAggregatorFactory<ApmFields>(); diff --git a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_service_assets_aggregator.ts b/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_service_assets_aggregator.ts deleted file mode 100644 index ab2e6a4cd9507..0000000000000 --- a/packages/kbn-apm-synthtrace/src/lib/assets/aggregators/create_traces_service_assets_aggregator.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { ApmFields, hashKeysOf } from '@kbn/apm-synthtrace-client'; -import { ServiceAssetDocument } from '@kbn/apm-synthtrace-client/src/lib/assets/service_assets'; -import { identity, noop } from 'lodash'; -import { createTracesAssetsAggregator } from './create_traces_assets_aggregator'; - -const KEY_FIELDS: Array<keyof ApmFields> = ['service.name']; - -export function createTracesServiceAssetsAggregator() { - return createTracesAssetsAggregator<ServiceAssetDocument>( - { - filter: (event) => event['processor.event'] === 'transaction', - getAggregateKey: (event) => { - // see https://github.com/elastic/apm-server/blob/main/x-pack/apm-server/aggregation/txmetrics/aggregator.go - return hashKeysOf(event as ApmFields, KEY_FIELDS as Array<keyof ApmFields>); - }, - init: (event, firstSeen, lastSeen) => { - return { - 'asset.id': event['service.name']!, - 'asset.type': 'service', - 'asset.identifying_metadata': ['service.name'], - 'asset.first_seen': firstSeen, - 'asset.last_seen': lastSeen, - 'asset.signalTypes': { - 'asset.traces': true, - }, - 'service.environment': event['service.environment'], - 'service.name': event['service.name']!, - 'service.node.name': event['service.node.name'], - 'service.language.name': event['service.language.name'], - }; - }, - }, - noop, - identity - ); -} diff --git a/packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts deleted file mode 100644 index c01653c6e7ee2..0000000000000 --- a/packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { Client } from '@elastic/elasticsearch'; -import { - ApmFields, - AssetDocument, - ESDocumentWithOperation, - LogDocument, -} from '@kbn/apm-synthtrace-client'; -import { merge } from 'lodash'; -import { PassThrough, pipeline, Readable, Transform } from 'stream'; -import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client'; -import { getDedotTransform } from '../shared/get_dedot_transform'; -import { getSerializeTransform } from '../shared/get_serialize_transform'; -import { Logger } from '../utils/create_logger'; -import { fork } from '../utils/stream_utils'; -import { createLogsServiceAssetsAggregator } from './aggregators/create_logs_service_assets_aggregator'; -import { createTracesServiceAssetsAggregator } from './aggregators/create_traces_service_assets_aggregator'; - -export type AssetsSynthtraceEsClientOptions = Omit<SynthtraceEsClientOptions, 'pipeline'>; - -export class AssetsSynthtraceEsClient extends SynthtraceEsClient<AssetDocument> { - constructor(options: { client: Client; logger: Logger } & AssetsSynthtraceEsClientOptions) { - super({ - ...options, - pipeline: assetsPipeline(), - }); - this.indices = ['assets']; - } -} - -function assetsPipeline() { - return (base: Readable) => { - const aggregators = [ - createTracesServiceAssetsAggregator(), - createLogsServiceAssetsAggregator(), - ]; - return pipeline( - base, - getSerializeTransform(), - fork(new PassThrough({ objectMode: true }), ...aggregators), - getAssetsFilterTransform(), - getMergeAssetsTransform(), - getRoutingTransform(), - getDedotTransform(), - (err: unknown) => { - if (err) { - throw err; - } - } - ); - }; -} - -function getAssetsFilterTransform() { - return new Transform({ - objectMode: true, - transform( - document: ESDocumentWithOperation<AssetDocument | ApmFields | LogDocument>, - encoding, - callback - ) { - if ('asset.id' in document) { - callback(null, document); - } else { - callback(); - } - }, - }); -} - -function getMergeAssetsTransform() { - const mergedDocuments: Record<string, AssetDocument> = {}; - return new Transform({ - objectMode: true, - transform(nextDocument: ESDocumentWithOperation<AssetDocument>, encoding, callback) { - const assetId = nextDocument['asset.id']; - if (!mergedDocuments[assetId]) { - mergedDocuments[assetId] = { ...nextDocument }; - } else { - const mergedDocument = mergedDocuments[assetId]; - mergedDocument['asset.signalTypes'] = merge( - mergedDocument['asset.signalTypes'], - nextDocument['asset.signalTypes'] - ); - } - callback(); - }, - flush(callback) { - Object.values(mergedDocuments).forEach((item) => this.push(item)); - callback(); - }, - }); -} - -function getRoutingTransform() { - return new Transform({ - objectMode: true, - transform(document: ESDocumentWithOperation<AssetDocument>, encoding, callback) { - if ('asset.type' in document) { - document._index = `assets`; - } else { - throw new Error(`Cannot determine index for event ${JSON.stringify(document)}`); - } - - callback(null, document); - }, - }); -} diff --git a/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts new file mode 100644 index 0000000000000..ea9c7a7f0e4a2 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Client } from '@elastic/elasticsearch'; +import { EntityFields, ESDocumentWithOperation } from '@kbn/apm-synthtrace-client'; +import { pipeline, Readable, Transform } from 'stream'; +import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client'; +import { getDedotTransform } from '../shared/get_dedot_transform'; +import { getSerializeTransform } from '../shared/get_serialize_transform'; +import { Logger } from '../utils/create_logger'; + +export type EntitiesSynthtraceEsClientOptions = Omit<SynthtraceEsClientOptions, 'pipeline'>; + +export class EntitiesSynthtraceEsClient extends SynthtraceEsClient<EntityFields> { + constructor(options: { client: Client; logger: Logger } & EntitiesSynthtraceEsClientOptions) { + super({ + ...options, + pipeline: entitiesPipeline(), + }); + this.indices = ['.entities.v1.latest.builtin*']; + } +} + +function entitiesPipeline() { + return (base: Readable) => { + return pipeline( + base, + getSerializeTransform(), + lastSeenTimestampTransform(), + getRoutingTransform(), + getDedotTransform(), + (err: unknown) => { + if (err) { + throw err; + } + } + ); + }; +} + +function lastSeenTimestampTransform() { + return new Transform({ + objectMode: true, + transform(document: ESDocumentWithOperation<EntityFields>, encoding, callback) { + const timestamp = document['@timestamp']; + if (timestamp) { + const isoString = new Date(timestamp).toISOString(); + document['entity.lastSeenTimestamp'] = isoString; + document['event.ingested'] = isoString; + delete document['@timestamp']; + } + callback(null, document); + }, + }); +} + +function getRoutingTransform() { + return new Transform({ + objectMode: true, + transform(document: ESDocumentWithOperation<EntityFields>, encoding, callback) { + const entityType: string | undefined = document['entity.type']; + if (entityType === undefined) { + throw new Error(`entity.type was not defined: ${JSON.stringify(document)}`); + } + const entityIndexName = `${entityType}s`; + document._action = { + index: { + _index: `.entities.v1.latest.builtin_${entityIndexName}_from_ecs_data`, + _id: document['entity.id'], + }, + }; + + callback(null, document); + }, + }); +} diff --git a/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts b/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts index a7bc682697eb3..ed6d1b813184b 100644 --- a/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts @@ -48,11 +48,7 @@ export class SynthtraceEsClient<TFields extends Fields> { } async clean() { - this.logger.info( - `Cleaning data streams "${this.dataStreams.join(',')}" and indices "${this.indices.join( - ',' - )}"` - ); + this.logger.info(`Cleaning data streams: "${this.dataStreams.join(',')}"`); const resolvedIndices = this.indices.length ? ( @@ -65,6 +61,10 @@ export class SynthtraceEsClient<TFields extends Fields> { ).indices.map((index: { name: string }) => index.name) : []; + if (resolvedIndices.length) { + this.logger.info(`Cleaning indices: "${resolvedIndices.join(',')}"`); + } + await Promise.all([ ...(this.dataStreams.length ? [ diff --git a/packages/kbn-apm-synthtrace/src/lib/utils/create_assets_aggregator_factory.ts b/packages/kbn-apm-synthtrace/src/lib/utils/create_assets_aggregator_factory.ts deleted file mode 100644 index fa0c8d3155130..0000000000000 --- a/packages/kbn-apm-synthtrace/src/lib/utils/create_assets_aggregator_factory.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { appendHash, AssetDocument, Fields } from '@kbn/apm-synthtrace-client'; -import { Duplex, PassThrough } from 'stream'; - -export function createAssetsAggregatorFactory<TFields extends Fields>() { - return function <TAsset extends AssetDocument>( - { - filter, - getAggregateKey, - init, - }: { - filter: (event: TFields) => boolean; - getAggregateKey: (event: TFields) => string; - init: (event: TFields, firstSeen: string, lastSeen: string) => TAsset; - }, - reduce: (asset: TAsset, event: TFields) => void, - serialize: (asset: TAsset) => TAsset - ) { - const assets: Map<string, TAsset> = new Map(); - let toFlush: TAsset[] = []; - let cb: (() => void) | undefined; - - function flush(stream: Duplex, includeCurrentAssets: boolean, callback?: () => void) { - const allItems = [...toFlush]; - - toFlush = []; - - if (includeCurrentAssets) { - allItems.push(...assets.values()); - assets.clear(); - } - - while (allItems.length) { - const next = allItems.shift()!; - const serialized = serialize(next); - const shouldWriteNext = stream.push(serialized); - if (!shouldWriteNext) { - toFlush = allItems; - cb = callback; - return; - } - } - - const next = cb; - cb = undefined; - next?.(); - callback?.(); - } - - const timeRanges: number[] = []; - - return new PassThrough({ - objectMode: true, - read() { - flush(this, false, cb); - }, - final(callback) { - flush(this, true, callback); - }, - write(event: TFields, encoding, callback) { - if (!filter(event)) { - callback(); - return; - } - timeRanges.push(event['@timestamp']!); - const firstSeen = new Date(Math.min(...timeRanges)).toISOString(); - const lastSeen = new Date(Math.max(...timeRanges)).toISOString(); - - const key = appendHash(getAggregateKey(event), ''); - - let asset = assets.get(key); - - if (asset) { - // @ts-ignore - asset['asset.last_seen'] = lastSeen; - } else { - asset = init({ ...event }, firstSeen, lastSeen); - assets.set(key, asset); - } - - reduce(asset, event); - callback(); - }, - }); - }; -} diff --git a/packages/kbn-apm-synthtrace/src/scenarios/traces_logs_assets.ts b/packages/kbn-apm-synthtrace/src/scenarios/traces_logs_entities.ts similarity index 63% rename from packages/kbn-apm-synthtrace/src/scenarios/traces_logs_assets.ts rename to packages/kbn-apm-synthtrace/src/scenarios/traces_logs_entities.ts index d7b22b11bb4c0..2e860a525c60a 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/traces_logs_assets.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/traces_logs_entities.ts @@ -9,72 +9,54 @@ import { apm, - ApmFields, generateLongId, generateShortId, - infra, Instance, log, - Serializable, + entities, + EntityFields, } from '@kbn/apm-synthtrace-client'; -import { random } from 'lodash'; import { Readable } from 'stream'; import { Scenario } from '../cli/scenario'; -import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; import { withClient } from '../lib/utils/with_client'; import { parseLogsScenarioOpts } from './helpers/logs_scenario_opts_parser'; +import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; const ENVIRONMENT = getSynthtraceEnvironment(__filename); -const scenario: Scenario<ApmFields> = async (runOptions) => { - const { logger, scenarioOpts } = runOptions; - const { numServices = 3, numHosts = 10 } = runOptions.scenarioOpts || {}; - const { isLogsDb } = parseLogsScenarioOpts(scenarioOpts); +const MESSAGE_LOG_LEVELS = [ + { message: 'A simple log with something random <random> in the middle', level: 'info' }, + { message: 'Yet another debug log', level: 'debug' }, + { message: 'Error with certificate: "ca_trusted_fingerprint"', level: 'error' }, +]; + +const SYNTH_JAVA_TRACE_ENTITY_ID = generateShortId(); +const SYNTH_NODE_TRACES_LOGS_ENTITY_ID = generateShortId(); +const SYNTH_GO_LOGS_ENTITY_ID = generateShortId(); + +const scenario: Scenario<Partial<EntityFields>> = async (runOptions) => { + const { logger } = runOptions; + const { isLogsDb } = parseLogsScenarioOpts(runOptions.scenarioOpts); return { - bootstrap: async ({ logsEsClient }) => { + bootstrap: async ({ entitiesKibanaClient, logsEsClient }) => { + await entitiesKibanaClient.installEntityIndexPatterns(); if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb); }, - generate: ({ - range, - clients: { apmEsClient, assetsEsClient, logsEsClient, infraEsClient }, - }) => { + generate: ({ range, clients: { entitiesEsClient, logsEsClient, apmEsClient } }) => { const transactionName = '240rpm/75% 1000ms'; + const entityHistoryTimestamps = range.interval('1m').rate(1); const successfulTimestamps = range.interval('1m').rate(1); const failedTimestamps = range.interval('1m').rate(1); - const serviceNames = [...Array(numServices).keys()].map((index) => `apm-only-${index}`); - serviceNames.push('multi-signal-service'); - const HOSTS = Array(numHosts) - .fill(0) - .map((_, idx) => infra.host(`my-host-${idx}`)); - - const hosts = range - .interval('30s') - .rate(1) - .generator((timestamp) => - HOSTS.flatMap((host) => [ - host.cpu().timestamp(timestamp), - host.memory().timestamp(timestamp), - host.network().timestamp(timestamp), - host.load().timestamp(timestamp), - host.filesystem().timestamp(timestamp), - host.diskio().timestamp(timestamp), - ]) - ); - const instances = serviceNames.map((serviceName) => - apm - .service({ name: serviceName, environment: ENVIRONMENT, agentName: 'nodejs' }) - .instance('instance') - ); - const instanceSpans = (instance: Instance, index: number) => { + const instanceSpans = (instance: Instance) => { const successfulTraceEvents = successfulTimestamps.generator((timestamp) => instance .transaction({ transactionName }) .timestamp(timestamp) - .duration(random(100, (index % 4) * 1000, false)) + .duration(1000) .success() .children( instance @@ -128,13 +110,25 @@ const scenario: Scenario<ApmFields> = async (runOptions) => { return [...successfulTraceEvents, ...failedTraceEvents, ...metricsets]; }; - const MESSAGE_LOG_LEVELS = [ - { message: 'A simple log with something random <random> in the middle', level: 'info' }, - { message: 'Yet another debug log', level: 'debug' }, - { message: 'Error with certificate: "ca_trusted_fingerprint"', level: 'error' }, - ]; + const SYNTH_JAVA_TRACE = 'synth-java-trace'; + const apmOnlyInstance = apm + .service({ name: SYNTH_JAVA_TRACE, agentName: 'java', environment: ENVIRONMENT }) + .instance('intance'); + const apmOnlyEvents = instanceSpans(apmOnlyInstance); + const synthJavaTraces = entities.serviceEntity({ + serviceName: SYNTH_JAVA_TRACE, + agentName: ['java'], + dataStreamType: ['traces'], + environment: ENVIRONMENT, + entityId: SYNTH_JAVA_TRACE_ENTITY_ID, + }); - const logsWithTraces = range + const SYNTH_NODE_TRACE_LOGS = 'synth-node-trace-logs'; + const apmAndLogsInstance = apm + .service({ name: SYNTH_NODE_TRACE_LOGS, agentName: 'nodejs', environment: ENVIRONMENT }) + .instance('intance'); + const apmAndLogsApmEvents = instanceSpans(apmAndLogsInstance); + const apmAndLogsLogsEvents = range .interval('1m') .rate(1) .generator((timestamp) => { @@ -153,14 +147,14 @@ const scenario: Scenario<ApmFields> = async (runOptions) => { .create({ isLogsDb }) .message(message.replace('<random>', generateShortId())) .logLevel(level) - .service('multi-signal-service') + .service(SYNTH_NODE_TRACE_LOGS) .defaults({ 'trace.id': generateShortId(), 'agent.name': 'nodejs', 'orchestrator.cluster.name': CLUSTER.clusterName, 'orchestrator.cluster.id': CLUSTER.clusterId, 'orchestrator.namespace': CLUSTER.namespace, - 'container.name': `${serviceNames[0]}-${generateShortId()}`, + 'container.name': `${SYNTH_NODE_TRACE_LOGS}-${generateShortId()}`, 'orchestrator.resource.id': generateShortId(), 'cloud.provider': 'gcp', 'cloud.region': 'eu-central-1', @@ -173,8 +167,16 @@ const scenario: Scenario<ApmFields> = async (runOptions) => { .timestamp(timestamp); }); }); + const synthNodeTracesLogs = entities.serviceEntity({ + serviceName: SYNTH_NODE_TRACE_LOGS, + agentName: ['nodejs'], + dataStreamType: ['traces', 'logs'], + environment: ENVIRONMENT, + entityId: SYNTH_NODE_TRACES_LOGS_ENTITY_ID, + }); - const logsOnly = range + const SYNTH_GO_LOGS = 'synth-go-logs'; + const logsEvents = range .interval('1m') .rate(1) .generator((timestamp) => { @@ -193,57 +195,67 @@ const scenario: Scenario<ApmFields> = async (runOptions) => { .create({ isLogsDb }) .message(message.replace('<random>', generateShortId())) .logLevel(level) - .service('logs-only-services') + .service(SYNTH_GO_LOGS) .defaults({ 'trace.id': generateShortId(), 'agent.name': 'nodejs', 'orchestrator.cluster.name': CLUSTER.clusterName, 'orchestrator.cluster.id': CLUSTER.clusterId, 'orchestrator.namespace': CLUSTER.namespace, - 'container.name': `logs-only-${generateShortId()}`, + 'container.name': `${SYNTH_GO_LOGS}-${generateShortId()}`, 'orchestrator.resource.id': generateShortId(), 'cloud.provider': 'gcp', 'cloud.region': 'eu-central-1', 'cloud.availability_zone': 'eu-central-1a', + 'log.level': 'error', 'cloud.project.id': generateShortId(), 'cloud.instance.id': generateShortId(), 'log.file.path': `/logs/${generateLongId()}/error.txt`, - 'log.level': 'error', }) .timestamp(timestamp); }); }); + const synthGoTraces = entities.serviceEntity({ + serviceName: SYNTH_GO_LOGS, + agentName: ['go'], + dataStreamType: ['logs'], + environment: ENVIRONMENT, + entityId: SYNTH_GO_LOGS_ENTITY_ID, + }); - function* createGeneratorFromArray(arr: Array<Serializable<any>>) { - yield* arr; - } - - const logsValuesArray = [...logsWithTraces, ...logsOnly]; - const logsGen = createGeneratorFromArray(logsValuesArray); - const logsGenAssets = createGeneratorFromArray(logsValuesArray); + const entitiesEvents = entityHistoryTimestamps.generator((timestamp) => { + return [ + synthNodeTracesLogs.timestamp(timestamp), + synthJavaTraces.timestamp(timestamp), + synthGoTraces.timestamp(timestamp), + ]; + }); - const traces = instances.flatMap((instance, index) => instanceSpans(instance, index)); - const tracesGen = createGeneratorFromArray(traces); - const tracesGenAssets = createGeneratorFromArray(traces); + const apmPython = apm + .service({ name: 'synth-python', agentName: 'python', environment: ENVIRONMENT }) + .instance('intance'); + const apmPythonEvents = instanceSpans(apmPython); return [ withClient( - assetsEsClient, - logger.perf('generating_assets_events', () => - Readable.from(Array.from(logsGenAssets).concat(Array.from(tracesGenAssets))) - ) + entitiesEsClient, + logger.perf('generating_entities_events', () => entitiesEvents) ), withClient( logsEsClient, - logger.perf('generating_logs', () => logsGen) + logger.perf('generating_logs', () => + Readable.from(Array.from(apmAndLogsLogsEvents).concat(Array.from(logsEvents))) + ) ), withClient( apmEsClient, - logger.perf('generating_apm_events', () => tracesGen) - ), - withClient( - infraEsClient, - logger.perf('generating_infra_hosts', () => hosts) + logger.perf('generating_apm_events', () => + Readable.from( + Array.from(apmOnlyEvents).concat( + Array.from(apmAndLogsApmEvents).concat(Array.from(apmPythonEvents)) + ) + ) + ) ), ]; }, diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index f46f9476ff2dd..ed95b792fb8c7 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -11,7 +11,7 @@ import { ApmSynthtraceEsClient, ApmSynthtraceKibanaClient, LogsSynthtraceEsClient, - AssetsSynthtraceEsClient, + EntitiesSynthtraceEsClient, createLogger, LogLevel, } from '@kbn/apm-synthtrace'; @@ -83,9 +83,9 @@ export interface CreateTest { context: InheritedFtrProviderContext ) => Promise<LogsSynthtraceEsClient>; synthtraceEsClient: (context: InheritedFtrProviderContext) => Promise<ApmSynthtraceEsClient>; - assetsSynthtraceEsClient: ( + entitiesSynthtraceEsClient: ( context: InheritedFtrProviderContext - ) => Promise<AssetsSynthtraceEsClient>; + ) => Promise<EntitiesSynthtraceEsClient>; apmSynthtraceEsClient: (context: InheritedFtrProviderContext) => Promise<ApmSynthtraceEsClient>; synthtraceKibanaClient: ( context: InheritedFtrProviderContext @@ -132,8 +132,8 @@ export function createTestConfig( logger: createLogger(LogLevel.info), refreshAfterIndex: true, }), - assetsSynthtraceEsClient: (context: InheritedFtrProviderContext) => - new AssetsSynthtraceEsClient({ + entitiesSynthtraceEsClient: (context: InheritedFtrProviderContext) => + new EntitiesSynthtraceEsClient({ client: context.getService('es'), logger: createLogger(LogLevel.info), refreshAfterIndex: true, From f0f17756324836e00ae0440ed1ba34c90490e843 Mon Sep 17 00:00:00 2001 From: natasha-moore-elastic <137783811+natasha-moore-elastic@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:29:05 +0100 Subject: [PATCH 43/84] [DOCS ]Direct users to new API reference site (#195909) ## Summary Contributes to https://github.com/elastic/security-docs-internal/issues/48. Add callouts to the asciidoc Osquery API docs to direct users to the new API reference site, in preparation for retiring the asciidoc API docs. NOTE: The api-kibana variable is defined in version-specific files. In [8.15.asciidoc](https://github.com/elastic/docs/blob/873ec2c47f905b5e18f5606fde0858a1f127a244/shared/versions/stack/8.15.asciidoc#L74) and [8.x.asciidoc](https://github.com/elastic/docs/blob/873ec2c47f905b5e18f5606fde0858a1f127a244/shared/versions/stack/8.x.asciidoc#L75), the variable points to the [v8 branch](https://www.elastic.co/docs/api/doc/kibana/v8) of the API reference, which currently doesn't include Security API docs. The v8 branch is derived from the "current" Kibana branch, which is currently 8.15. This likely means that we can only backport the callouts to 8.16 once 8.16 becomes the "current" docs version. Preview: [Osquery manager API](https://kibana_bk_195909.docs-preview.app.elstc.co/guide/en/kibana/master/osquery-manager-api.html) and all its child pages --- docs/api/osquery-manager.asciidoc | 6 ++++++ docs/api/osquery-manager/live-queries/create.asciidoc | 6 ++++++ docs/api/osquery-manager/live-queries/get-all.asciidoc | 6 ++++++ docs/api/osquery-manager/live-queries/get-results.asciidoc | 6 ++++++ docs/api/osquery-manager/live-queries/get.asciidoc | 6 ++++++ docs/api/osquery-manager/packs/create.asciidoc | 6 ++++++ docs/api/osquery-manager/packs/delete.asciidoc | 6 ++++++ docs/api/osquery-manager/packs/get-all.asciidoc | 6 ++++++ docs/api/osquery-manager/packs/get.asciidoc | 6 ++++++ docs/api/osquery-manager/packs/update.asciidoc | 6 ++++++ docs/api/osquery-manager/saved-queries/create.asciidoc | 6 ++++++ docs/api/osquery-manager/saved-queries/delete.asciidoc | 6 ++++++ docs/api/osquery-manager/saved-queries/get-all.asciidoc | 6 ++++++ docs/api/osquery-manager/saved-queries/get.asciidoc | 6 ++++++ docs/api/osquery-manager/saved-queries/update.asciidoc | 6 ++++++ 15 files changed, 90 insertions(+) diff --git a/docs/api/osquery-manager.asciidoc b/docs/api/osquery-manager.asciidoc index 2607bdad1f54f..3e7176e30f31f 100644 --- a/docs/api/osquery-manager.asciidoc +++ b/docs/api/osquery-manager.asciidoc @@ -1,6 +1,12 @@ [[osquery-manager-api]] == Osquery manager API +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Run live queries, manage packs and saved queries Use the osquery manager APIs for managing packs and saved queries. diff --git a/docs/api/osquery-manager/live-queries/create.asciidoc b/docs/api/osquery-manager/live-queries/create.asciidoc index c080cfe08a903..fcddf247e3e8e 100644 --- a/docs/api/osquery-manager/live-queries/create.asciidoc +++ b/docs/api/osquery-manager/live-queries/create.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Create live query</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Create live queries. diff --git a/docs/api/osquery-manager/live-queries/get-all.asciidoc b/docs/api/osquery-manager/live-queries/get-all.asciidoc index 58845d3c498e6..3586c52577ae3 100644 --- a/docs/api/osquery-manager/live-queries/get-all.asciidoc +++ b/docs/api/osquery-manager/live-queries/get-all.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get live queries</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Get live queries. diff --git a/docs/api/osquery-manager/live-queries/get-results.asciidoc b/docs/api/osquery-manager/live-queries/get-results.asciidoc index 9c7fa1833e0de..53fcaa35abf09 100644 --- a/docs/api/osquery-manager/live-queries/get-results.asciidoc +++ b/docs/api/osquery-manager/live-queries/get-results.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get live query results</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Retrieve a single live query result by ID. diff --git a/docs/api/osquery-manager/live-queries/get.asciidoc b/docs/api/osquery-manager/live-queries/get.asciidoc index 8cf5a3abd1c3c..b2a1e9bf7bfd1 100644 --- a/docs/api/osquery-manager/live-queries/get.asciidoc +++ b/docs/api/osquery-manager/live-queries/get.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get live query</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Retrieves a single live query by ID. diff --git a/docs/api/osquery-manager/packs/create.asciidoc b/docs/api/osquery-manager/packs/create.asciidoc index 84e8c3e71eb5c..c23d2e40a4ba2 100644 --- a/docs/api/osquery-manager/packs/create.asciidoc +++ b/docs/api/osquery-manager/packs/create.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Create pack</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Create packs. diff --git a/docs/api/osquery-manager/packs/delete.asciidoc b/docs/api/osquery-manager/packs/delete.asciidoc index ae0834e6f2b4a..8a7832d91e3c7 100644 --- a/docs/api/osquery-manager/packs/delete.asciidoc +++ b/docs/api/osquery-manager/packs/delete.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Delete pack</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Delete packs. WARNING: Once you delete a pack, _it cannot be recovered_. diff --git a/docs/api/osquery-manager/packs/get-all.asciidoc b/docs/api/osquery-manager/packs/get-all.asciidoc index 44c36947f46b0..bf007d44e61a1 100644 --- a/docs/api/osquery-manager/packs/get-all.asciidoc +++ b/docs/api/osquery-manager/packs/get-all.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get packs</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Get packs. diff --git a/docs/api/osquery-manager/packs/get.asciidoc b/docs/api/osquery-manager/packs/get.asciidoc index 795adef90e24d..6686751d6902e 100644 --- a/docs/api/osquery-manager/packs/get.asciidoc +++ b/docs/api/osquery-manager/packs/get.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get pack</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Retrieve a single pack by ID. diff --git a/docs/api/osquery-manager/packs/update.asciidoc b/docs/api/osquery-manager/packs/update.asciidoc index d098d2567f1ac..2e7f6004fd008 100644 --- a/docs/api/osquery-manager/packs/update.asciidoc +++ b/docs/api/osquery-manager/packs/update.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Update pack</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Update packs. WARNING: You are unable to update a prebuilt pack (`read_only = true`). diff --git a/docs/api/osquery-manager/saved-queries/create.asciidoc b/docs/api/osquery-manager/saved-queries/create.asciidoc index 75b764ded6023..e137c6cb78484 100644 --- a/docs/api/osquery-manager/saved-queries/create.asciidoc +++ b/docs/api/osquery-manager/saved-queries/create.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Create saved query</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Create saved queries. diff --git a/docs/api/osquery-manager/saved-queries/delete.asciidoc b/docs/api/osquery-manager/saved-queries/delete.asciidoc index 5518159a1aa1b..7d0b36de0405d 100644 --- a/docs/api/osquery-manager/saved-queries/delete.asciidoc +++ b/docs/api/osquery-manager/saved-queries/delete.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Delete saved query</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Delete saved queries. WARNING: Once you delete a saved query, _it cannot be recovered_. diff --git a/docs/api/osquery-manager/saved-queries/get-all.asciidoc b/docs/api/osquery-manager/saved-queries/get-all.asciidoc index 3fc8d1d5da93c..829ee51c6d6e4 100644 --- a/docs/api/osquery-manager/saved-queries/get-all.asciidoc +++ b/docs/api/osquery-manager/saved-queries/get-all.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get saved-queries</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Get saved queries. diff --git a/docs/api/osquery-manager/saved-queries/get.asciidoc b/docs/api/osquery-manager/saved-queries/get.asciidoc index c1d2cd43bab86..b9764c8d27a3f 100644 --- a/docs/api/osquery-manager/saved-queries/get.asciidoc +++ b/docs/api/osquery-manager/saved-queries/get.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Get saved query</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Retrieve a single saved query by ID. diff --git a/docs/api/osquery-manager/saved-queries/update.asciidoc b/docs/api/osquery-manager/saved-queries/update.asciidoc index 025a69b28e0f0..b7d620efc7692 100644 --- a/docs/api/osquery-manager/saved-queries/update.asciidoc +++ b/docs/api/osquery-manager/saved-queries/update.asciidoc @@ -4,6 +4,12 @@ <titleabbrev>Update saved query</titleabbrev> ++++ +.New API Reference +[sidebar] +-- +For the most up-to-date API details, refer to {api-kibana}/group/endpoint-security-osquery-api[Osquery APIs]. +-- + experimental[] Update saved queries. WARNING: You are unable to update a prebuilt saved query (`prebuilt = true`). From 58b2c6ebde0ee14e94e73549454911aaf7cd9dd8 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde <tiago.vilaverde@elastic.co> Date: Tue, 15 Oct 2024 17:42:39 +0200 Subject: [PATCH 44/84] [Entity Store] Enablement UI (#196076) ### Entity store enablement UI This PR adds a UI to enable the Entity Store. ### How to test 1. Enable `entityStoreEnabled` experimental feature flag 2. Navigate to `Security > Dashboards > Entity Analytics` 3. Work through the distinct flows to enable the store * For example, choose to enable risk score together with the store 4. Navigate to `Security > Manage > Entity Store` to start/stop the store 5. Validate that the appropriate transforms and pipelines have been initialized and have the correct status (for example, via the Stack Management UI) EDIT: Enablement flow screenshots: #### Enable both risk score and entity store ![Screenshot 2024-10-15 at 12 14 40](https://github.com/user-attachments/assets/90ab2eaa-dd73-47b4-b940-c9549422e37c) #### Enable Risk score only (Entity store already enabled) ![Screenshot 2024-10-15 at 12 15 04](https://github.com/user-attachments/assets/3ef31857-7515-4636-adde-f6c6e7f7c13b) #### Modal to choose what to enable ![Screenshot 2024-10-15 at 12 14 48](https://github.com/user-attachments/assets/1746767a-cfb0-41c0-823c-cafac45bd901) #### New Entity Store management page ![Screenshot 2024-10-15 at 12 14 08](https://github.com/user-attachments/assets/aa2b8c63-1fcf-4a18-87d2-cecceaabd6cd) --------- Co-authored-by: jaredburgettelastic <jared.burgett@elastic.co> Co-authored-by: machadoum <pablo.nevesmachado@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Mark Hopkin <mark.hopkin@elastic.co> Co-authored-by: natasha-moore-elastic <137783811+natasha-moore-elastic@users.noreply.github.com> --- .../output/kibana.serverless.staging.yaml | 1 + oas_docs/output/kibana.serverless.yaml | 1 + oas_docs/output/kibana.staging.yaml | 1 + oas_docs/output/kibana.yaml | 1 + packages/deeplinks/security/deep_links.ts | 1 + .../entity_store/common.gen.ts | 2 +- .../entity_store/common.schema.yaml | 1 + .../security_solution/common/constants.ts | 2 + ...alytics_api_2023_10_31.bundled.schema.yaml | 1 + ...alytics_api_2023_10_31.bundled.schema.yaml | 1 + .../app/solution_navigation/categories.ts | 2 +- .../links/sections/settings_links.ts | 2 +- .../public/app/translations.ts | 4 + .../public/common/links/links.test.tsx | 2 +- .../entity_analytics/api/entity_store.ts | 68 +++ .../components/dashboard_panels.tsx | 249 ++++++++++ .../components/enablement_modal.tsx | 141 ++++++ .../components/entity_source_filter.tsx | 2 +- .../components/entity_store/entities_list.tsx | 4 +- .../hooks/use_entities_list_columns.tsx | 13 +- .../hooks/use_entities_list_filters.test.ts | 35 +- .../hooks/use_entities_list_filters.ts | 51 +- .../hooks/use_entity_engine_status.ts | 58 +++ .../entity_store/hooks/use_entity_store.ts | 126 +++++ .../components/entity_store/translations.ts | 64 +++ .../images/entity_store_dashboard.png | Bin 0 -> 49832 bytes .../pages/asset_criticality_upload_page.tsx | 186 ------- .../pages/entity_analytics_dashboard.tsx | 32 +- .../pages/entity_store_management_page.tsx | 456 ++++++++++++++++++ .../public/entity_analytics/routes.tsx | 33 +- .../components/paginated_table/index.tsx | 2 +- .../public/management/links.ts | 23 +- .../entity_store/constants.ts | 8 +- .../entity_store/entity_store_data_client.ts | 180 ++++--- .../entity_store/routes/delete.ts | 5 +- .../saved_object/engine_descriptor.ts | 24 +- .../public/navigation/side_navigation.ts | 2 +- .../public/navigation/management_cards.ts | 2 +- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../asset_criticality_upload_page.cy.ts | 2 +- .../enable_risk_score_redirect.cy.ts | 57 ++- .../dashboards/upgrade_risk_score.cy.ts | 129 ++--- .../cypress/screens/asset_criticality.ts | 2 +- 45 files changed, 1552 insertions(+), 433 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/images/entity_store_dashboard.png delete mode 100644 x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index a4362db15cc7d..6df65e8ae2e3e 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -48009,6 +48009,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index a4362db15cc7d..6df65e8ae2e3e 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -48009,6 +48009,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 16a6a94d34d81..76e217fcba16d 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -56775,6 +56775,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 16a6a94d34d81..76e217fcba16d 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -56775,6 +56775,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/packages/deeplinks/security/deep_links.ts b/packages/deeplinks/security/deep_links.ts index 54b18dcaf9206..644691bd5b8bc 100644 --- a/packages/deeplinks/security/deep_links.ts +++ b/packages/deeplinks/security/deep_links.ts @@ -86,6 +86,7 @@ export enum SecurityPageName { entityAnalytics = 'entity_analytics', entityAnalyticsManagement = 'entity_analytics-management', entityAnalyticsAssetClassification = 'entity_analytics-asset-classification', + entityAnalyticsEntityStoreManagement = 'entity_analytics-entity_store_management', coverageOverview = 'coverage-overview', notes = 'notes', } diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index ed0806b798dd6..2dd83ca89bee0 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -25,7 +25,7 @@ export type IndexPattern = z.infer<typeof IndexPattern>; export const IndexPattern = z.string(); export type EngineStatus = z.infer<typeof EngineStatus>; -export const EngineStatus = z.enum(['installing', 'started', 'stopped', 'updating']); +export const EngineStatus = z.enum(['installing', 'started', 'stopped', 'updating', 'error']); export type EngineStatusEnum = typeof EngineStatus.enum; export const EngineStatusEnum = EngineStatus.enum; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index b06f484e4e29a..810961392aad1 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -38,6 +38,7 @@ components: - started - stopped - updating + - error IndexPattern: type: string diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e7bb823c04ec8..d4cb8f088df88 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -124,6 +124,8 @@ export const ENTITY_ANALYTICS_PATH = '/entity_analytics' as const; export const ENTITY_ANALYTICS_MANAGEMENT_PATH = `/entity_analytics_management` as const; export const ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH = `/entity_analytics_asset_criticality` as const; +export const ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH = + `/entity_analytics_entity_store` as const; export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const; export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}` as const; export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}` as const; diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 730ea240fe7b7..d3cce9170ae6a 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -806,6 +806,7 @@ components: - started - stopped - updating + - error type: string Entity: oneOf: diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 2522f3cb192ae..eecca3fe07ae6 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -806,6 +806,7 @@ components: - started - stopped - updating + - error type: string Entity: oneOf: diff --git a/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts b/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts index 86324b9ce9924..8d815ded5a3c4 100644 --- a/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts +++ b/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts @@ -50,7 +50,7 @@ export const CATEGORIES: Array<SeparatorLinkCategory<SolutionPageName>> = [ type: LinkCategoryType.separator, linkIds: [ SecurityPageName.entityAnalyticsManagement, - SecurityPageName.entityAnalyticsAssetClassification, + SecurityPageName.entityAnalyticsEntityStoreManagement, ], // Linked from the management cards landing. }, ]; diff --git a/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts b/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts index ed08596fe6c79..abe7cc68603bd 100644 --- a/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts +++ b/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts @@ -12,7 +12,7 @@ import * as i18n from './settings_translations'; const ENTITY_ANALYTICS_LINKS = [ SecurityPageName.entityAnalyticsManagement, - SecurityPageName.entityAnalyticsAssetClassification, + SecurityPageName.entityAnalyticsEntityStoreManagement, ]; export const createSettingsLinksFromManage = (manageLink: LinkItem): LinkItem[] => { diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 97f07ee6706b9..709bb5f614f7b 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -25,6 +25,10 @@ export const ENTITY_ANALYTICS_RISK_SCORE = i18n.translate( } ); +export const ENTITY_STORE = i18n.translate('xpack.securitySolution.navigation.entityStore', { + defaultMessage: 'Entity Store', +}); + export const NOTES = i18n.translate('xpack.securitySolution.navigation.notes', { defaultMessage: 'Notes', }); diff --git a/x-pack/plugins/security_solution/public/common/links/links.test.tsx b/x-pack/plugins/security_solution/public/common/links/links.test.tsx index aeffa1d44f823..c0f8c8cc48da4 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.test.tsx +++ b/x-pack/plugins/security_solution/public/common/links/links.test.tsx @@ -547,7 +547,7 @@ describe('Security links', () => { describe('isLinkUiSettingsAllowed', () => { const SETTING_KEY = 'test setting'; const mockedLink: LinkItem = { - id: SecurityPageName.entityAnalyticsAssetClassification, + id: SecurityPageName.entityAnalyticsEntityStoreManagement, title: 'test title', path: '/test_path', }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts new file mode 100644 index 0000000000000..34789402c89a5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts @@ -0,0 +1,68 @@ +/* + * 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 { useMemo } from 'react'; +import type { + DeleteEntityEngineResponse, + EntityType, + GetEntityEngineResponse, + InitEntityEngineResponse, + ListEntityEnginesResponse, + StopEntityEngineResponse, +} from '../../../common/api/entity_analytics'; +import { API_VERSIONS } from '../../../common/entity_analytics/constants'; +import { useKibana } from '../../common/lib/kibana/kibana_react'; + +export const useEntityStoreRoutes = () => { + const http = useKibana().services.http; + + return useMemo(() => { + const initEntityStore = async (entityType: EntityType) => { + return http.fetch<InitEntityEngineResponse>(`/api/entity_store/engines/${entityType}/init`, { + method: 'POST', + version: API_VERSIONS.public.v1, + body: JSON.stringify({}), + }); + }; + + const stopEntityStore = async (entityType: EntityType) => { + return http.fetch<StopEntityEngineResponse>(`/api/entity_store/engines/${entityType}/stop`, { + method: 'POST', + version: API_VERSIONS.public.v1, + body: JSON.stringify({}), + }); + }; + + const getEntityEngine = async (entityType: EntityType) => { + return http.fetch<GetEntityEngineResponse>(`/api/entity_store/engines/${entityType}`, { + method: 'GET', + version: API_VERSIONS.public.v1, + }); + }; + + const deleteEntityEngine = async (entityType: EntityType) => { + return http.fetch<DeleteEntityEngineResponse>(`/api/entity_store/engines/${entityType}`, { + method: 'DELETE', + version: API_VERSIONS.public.v1, + }); + }; + + const listEntityEngines = async () => { + return http.fetch<ListEntityEnginesResponse>(`/api/entity_store/engines`, { + method: 'GET', + version: API_VERSIONS.public.v1, + }); + }; + + return { + initEntityStore, + stopEntityStore, + getEntityEngine, + deleteEntityEngine, + listEntityEngines, + }; + }, [http]); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx new file mode 100644 index 0000000000000..3b4f661e949f2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx @@ -0,0 +1,249 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState } from 'react'; +import { + EuiEmptyPrompt, + EuiToolTip, + EuiButton, + EuiLoadingSpinner, + EuiFlexItem, + EuiFlexGroup, + EuiLoadingLogo, + EuiPanel, + EuiImage, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; + +import { EntitiesList } from '../entities_list'; + +import { useEntityStoreEnablement } from '../hooks/use_entity_store'; +import { EntityStoreEnablementModal, type Enablements } from './enablement_modal'; + +import { EntityAnalyticsRiskScores } from '../../entity_analytics_risk_score'; +import { useInitRiskEngineMutation } from '../../../api/hooks/use_init_risk_engine_mutation'; +import { useEntityEngineStatus } from '../hooks/use_entity_engine_status'; + +import dashboardEnableImg from '../../../images/entity_store_dashboard.png'; +import { + ENABLEMENT_DESCRIPTION_BOTH, + ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY, + ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY, + ENABLEMENT_INITIALIZING_ENTITY_STORE, + ENABLEMENT_INITIALIZING_RISK_ENGINE, + ENABLE_ALL_TITLE, + ENABLE_ENTITY_STORE_TITLE, + ENABLE_RISK_SCORE_TITLE, +} from '../translations'; +import { useRiskEngineStatus } from '../../../api/hooks/use_risk_engine_status'; + +const EntityStoreDashboardPanelsComponent = () => { + const [modal, setModalState] = useState({ visible: false }); + const [riskEngineInitializing, setRiskEngineInitializing] = useState(false); + + const entityStore = useEntityEngineStatus(); + const riskEngineStatus = useRiskEngineStatus(); + + const { enable: enableStore } = useEntityStoreEnablement(); + const { mutate: initRiskEngine } = useInitRiskEngineMutation(); + + const enableEntityStore = (enable: Enablements) => () => { + setModalState({ visible: false }); + if (enable.riskScore) { + const options = { + onSuccess: () => { + setRiskEngineInitializing(false); + if (enable.entityStore) { + enableStore(); + } + }, + }; + setRiskEngineInitializing(true); + initRiskEngine(undefined, options); + } + + if (enable.entityStore) { + enableStore(); + } + }; + + if (entityStore.status === 'loading') { + return ( + <EuiPanel hasBorder> + <EuiEmptyPrompt + icon={<EuiLoadingSpinner size="xl" />} + title={<h2>{ENABLEMENT_INITIALIZING_ENTITY_STORE}</h2>} + /> + </EuiPanel> + ); + } + + if (entityStore.status === 'installing') { + return ( + <EuiPanel hasBorder> + <EuiEmptyPrompt + icon={<EuiLoadingLogo logo="logoElastic" size="xl" />} + title={<h2>{ENABLEMENT_INITIALIZING_ENTITY_STORE}</h2>} + body={ + <p> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStore.enablement.initializing.description" + defaultMessage="This can take up to 5 minutes." + /> + </p> + } + /> + </EuiPanel> + ); + } + + const isRiskScoreAvailable = + riskEngineStatus.data && + riskEngineStatus.data.risk_engine_status !== RiskEngineStatusEnum.NOT_INSTALLED; + + return ( + <EuiFlexGroup direction="column" data-test-subj="entityStorePanelsGroup"> + {entityStore.status === 'enabled' && isRiskScoreAvailable && ( + <> + <EuiFlexItem> + <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.user} /> + </EuiFlexItem> + <EuiFlexItem> + <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.host} /> + </EuiFlexItem> + <EuiFlexItem> + <EntitiesList /> + </EuiFlexItem> + </> + )} + {entityStore.status === 'enabled' && !isRiskScoreAvailable && ( + <> + <EuiFlexItem> + <EnableEntityStore + onEnable={() => setModalState({ visible: true })} + loadingRiskEngine={riskEngineInitializing} + enablements="riskScore" + /> + </EuiFlexItem> + + <EuiFlexItem> + <EntitiesList /> + </EuiFlexItem> + </> + )} + + {entityStore.status === 'not_installed' && !isRiskScoreAvailable && ( + // TODO: Move modal inside EnableEntityStore component, eliminating the onEnable prop in favour of forwarding the riskScoreEnabled status + <EnableEntityStore + enablements="both" + onEnable={() => setModalState({ visible: true })} + loadingRiskEngine={riskEngineInitializing} + /> + )} + + {entityStore.status === 'not_installed' && isRiskScoreAvailable && ( + <> + <EuiFlexItem> + <EnableEntityStore + enablements="store" + onEnable={() => + setModalState({ + visible: true, + }) + } + /> + </EuiFlexItem> + <EuiFlexItem> + <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.user} /> + </EuiFlexItem> + <EuiFlexItem> + <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.host} /> + </EuiFlexItem> + </> + )} + + <EntityStoreEnablementModal + visible={modal.visible} + toggle={(visible) => setModalState({ visible })} + enableStore={enableEntityStore} + riskScore={{ disabled: isRiskScoreAvailable, checked: !isRiskScoreAvailable }} + entityStore={{ + disabled: entityStore.status === 'enabled', + checked: entityStore.status !== 'enabled', + }} + /> + </EuiFlexGroup> + ); +}; + +interface EnableEntityStoreProps { + onEnable: () => void; + enablements: 'store' | 'riskScore' | 'both'; + loadingRiskEngine?: boolean; +} + +export const EnableEntityStore: React.FC<EnableEntityStoreProps> = ({ + onEnable, + enablements, + loadingRiskEngine, +}) => { + const title = + enablements === 'store' + ? ENABLE_ENTITY_STORE_TITLE + : enablements === 'riskScore' + ? ENABLE_RISK_SCORE_TITLE + : ENABLE_ALL_TITLE; + + const body = + enablements === 'store' + ? ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY + : enablements === 'riskScore' + ? ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY + : ENABLEMENT_DESCRIPTION_BOTH; + + if (loadingRiskEngine) { + return ( + <EuiPanel hasBorder> + <EuiEmptyPrompt + icon={<EuiLoadingLogo logo="logoElastic" size="xl" />} + title={<h2>{ENABLEMENT_INITIALIZING_RISK_ENGINE}</h2>} + /> + </EuiPanel> + ); + } + return ( + <EuiEmptyPrompt + css={{ minWidth: '100%' }} + hasBorder + layout="horizontal" + className="eui-fullWidth" + title={<h2>{title}</h2>} + body={<p>{body}</p>} + actions={ + <EuiToolTip content={title}> + <EuiButton + color="primary" + fill + onClick={onEnable} + data-test-subj={`enable_entity_store_btn`} + > + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStore.enablement.enableButton" + defaultMessage="Enable" + /> + </EuiButton> + </EuiToolTip> + } + icon={<EuiImage size="l" hasShadow src={dashboardEnableImg} alt={title} />} + /> + ); +}; + +export const EntityStoreDashboardPanels = React.memo(EntityStoreDashboardPanelsComponent); +EntityStoreDashboardPanels.displayName = 'EntityStoreDashboardPanels'; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx new file mode 100644 index 0000000000000..94a3b6cd48edf --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx @@ -0,0 +1,141 @@ +/* + * 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 { + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiFlexGroup, + EuiFlexItem, + EuiSwitch, + EuiModalFooter, + EuiButton, + EuiHorizontalRule, + EuiText, + EuiButtonEmpty, + EuiBetaBadge, + EuiToolTip, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../../../common/translations'; +import { + ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY, + ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY, +} from '../translations'; + +export interface Enablements { + riskScore: boolean; + entityStore: boolean; +} + +interface EntityStoreEnablementModalProps { + visible: boolean; + toggle: (visible: boolean) => void; + enableStore: (enablements: Enablements) => () => void; + riskScore: { + disabled?: boolean; + checked?: boolean; + }; + entityStore: { + disabled?: boolean; + checked?: boolean; + }; +} + +export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProps> = ({ + visible, + toggle, + enableStore, + riskScore, + entityStore, +}) => { + const [enablements, setEnablements] = useState({ + riskScore: !!riskScore.checked, + entityStore: !!entityStore.checked, + }); + + if (!visible) { + return null; + } + return ( + <EuiModal onClose={() => toggle(false)}> + <EuiModalHeader> + <EuiModalHeaderTitle> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.enablements.modal.title" + defaultMessage="Additional charges may apply" + /> + </EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <EuiFlexGroup direction="column"> + <EuiText> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.enablements.modal.description" + defaultMessage="Please be aware that activating these features may incur additional charges depending on your subscription plan. Review your plan details carefully to avoid unexpected costs before proceeding." + /> + </EuiText> + <EuiHorizontalRule margin="none" /> + <EuiFlexItem> + <EuiSwitch + label={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.enablements.modal.risk" + defaultMessage="Risk Score" + /> + } + checked={enablements.riskScore} + disabled={riskScore.disabled || false} + onChange={() => setEnablements((prev) => ({ ...prev, riskScore: !prev.riskScore }))} + /> + </EuiFlexItem> + <EuiFlexItem> + <EuiText>{ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY}</EuiText> + </EuiFlexItem> + <EuiHorizontalRule margin="none" /> + + <EuiFlexItem> + <EuiFlexGroup justifyContent="flexStart"> + <EuiSwitch + label={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.enablements.modal.store" + defaultMessage="Entity Store" + /> + } + checked={enablements.entityStore} + disabled={entityStore.disabled || false} + onChange={() => + setEnablements((prev) => ({ ...prev, entityStore: !prev.entityStore })) + } + /> + <EuiToolTip content={TECHNICAL_PREVIEW_TOOLTIP}> + <EuiBetaBadge label={TECHNICAL_PREVIEW} /> + </EuiToolTip> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiText>{ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY}</EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiModalBody> + + <EuiModalFooter> + <EuiButtonEmpty onClick={() => toggle(false)}>{'Cancel'}</EuiButtonEmpty> + <EuiButton onClick={enableStore(enablements)} fill> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.enablements.modal.enable" + defaultMessage="Enable" + /> + </EuiButton> + </EuiModalFooter> + </EuiModal> + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx index b324adca0945e..aac8aad170f3f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx @@ -18,7 +18,7 @@ export enum EntitySource { CSV_UPLOAD = 'CSV upload', EVENTS = 'Events', } - +// TODO Fix the Entity Source field before using it export const EntitySourceFilter: React.FC<SourceFilterProps> = ({ selectedItems, onChange }) => { return ( <MultiselectFilter<EntitySource> diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx index c02cbbb930c5c..a6e058af34392 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx @@ -22,7 +22,6 @@ import type { Criteria } from '../../../explore/components/paginated_table'; import { PaginatedTable } from '../../../explore/components/paginated_table'; import { SeverityFilter } from '../severity/severity_filter'; import type { EntitySource } from './components/entity_source_filter'; -import { EntitySourceFilter } from './components/entity_source_filter'; import { useEntitiesListFilters } from './hooks/use_entities_list_filters'; import { AssetCriticalityFilter } from '../asset_criticality/asset_criticality_filter'; import { useEntitiesListQuery } from './hooks/use_entities_list_query'; @@ -41,7 +40,7 @@ export const EntitiesList: React.FC = () => { const [selectedSeverities, setSelectedSeverities] = useState<RiskSeverity[]>([]); const [selectedCriticalities, setSelectedCriticalities] = useState<CriticalityLevels[]>([]); - const [selectedSources, setSelectedSources] = useState<EntitySource[]>([]); + const [selectedSources, _] = useState<EntitySource[]>([]); const filter = useEntitiesListFilters({ selectedSeverities, @@ -148,7 +147,6 @@ export const EntitiesList: React.FC = () => { selectedItems={selectedCriticalities} onChange={setSelectedCriticalities} /> - <EntitySourceFilter selectedItems={selectedSources} onChange={setSelectedSources} /> </EuiFilterGroup> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx index cebc55693c9e8..52439d10a0000 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx @@ -20,6 +20,7 @@ import type { Entity } from '../../../../../common/api/entity_analytics/entity_s import type { CriticalityLevels } from '../../../../../common/constants'; import { ENTITIES_LIST_TABLE_ID } from '../constants'; import { isUserEntity } from '../helpers'; +import { CRITICALITY_LEVEL_TITLE } from '../../asset_criticality/translations'; export type EntitiesListColumns = [ Columns<Entity>, @@ -86,6 +87,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { /> ), sortable: true, + truncateText: { lines: 2 }, render: (_: string, record: Entity) => { return ( <span> @@ -94,7 +96,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { </span> ); }, - width: '30%', + width: '25%', }, { field: 'entity.source', @@ -104,7 +106,8 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { defaultMessage="Source" /> ), - width: '10%', + width: '25%', + truncateText: { lines: 2 }, render: (source: string | undefined) => { if (source != null) { return <span>{source}</span>; @@ -124,7 +127,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { width: '10%', render: (criticality: CriticalityLevels) => { if (criticality != null) { - return criticality; + return <span>{CRITICALITY_LEVEL_TITLE[criticality]}</span>; } return getEmptyTagValue(); @@ -173,7 +176,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { }, }, { - field: 'entity.lastSeenTimestamp', + field: '@timestamp', name: ( <FormattedMessage id="xpack.securitySolution.entityAnalytics.entityStore.entitiesList.lastUpdateColumn.title" @@ -184,7 +187,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { render: (lastUpdate: string) => { return <FormattedRelativePreferenceDate value={lastUpdate} />; }, - width: '25%', + width: '15%', }, ]; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts index f2fcd3e4f7685..de5f706d4524c 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts @@ -22,7 +22,7 @@ describe('useEntitiesListFilters', () => { mockUseGlobalFilterQuery.mockReturnValue({ filterQuery: null }); }); - it('should return empty array when no filters are selected', () => { + it('should return empty filter when no filters are selected', () => { const { result } = renderHook(() => useEntitiesListFilters({ selectedSeverities: [], @@ -49,13 +49,6 @@ describe('useEntitiesListFilters', () => { should: [ { term: { 'host.risk.calculated_level': RiskSeverity.Low } }, { term: { 'user.risk.calculated_level': RiskSeverity.Low } }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ { term: { 'host.risk.calculated_level': RiskSeverity.High } }, { term: { 'user.risk.calculated_level': RiskSeverity.High } }, ], @@ -77,8 +70,23 @@ describe('useEntitiesListFilters', () => { ); const expectedFilters: QueryDslQueryContainer[] = [ - { term: { 'asset.criticality': CriticalityLevels.EXTREME_IMPACT } }, - { term: { 'asset.criticality': CriticalityLevels.MEDIUM_IMPACT } }, + { + bool: { + minimum_should_match: 1, + should: [ + { + term: { + 'asset.criticality': CriticalityLevels.EXTREME_IMPACT, + }, + }, + { + term: { + 'asset.criticality': CriticalityLevels.MEDIUM_IMPACT, + }, + }, + ], + }, + }, ]; expect(result.current).toEqual(expectedFilters); @@ -138,7 +146,12 @@ describe('useEntitiesListFilters', () => { minimum_should_match: 1, }, }, - { term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }, + { + bool: { + should: [{ term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }], + minimum_should_match: 1, + }, + }, { term: { 'entity.source': EntitySource.CSV_UPLOAD } }, globalQuery, ]; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts index 7e9c25441a501..634f3f61c1590 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts @@ -26,11 +26,20 @@ export const useEntitiesListFilters = ({ const { filterQuery: globalQuery } = useGlobalFilterQuery(); return useMemo(() => { - const criticalityFilter: QueryDslQueryContainer[] = selectedCriticalities.map((value) => ({ - term: { - 'asset.criticality': value, - }, - })); + const criticalityFilter: QueryDslQueryContainer[] = selectedCriticalities.length + ? [ + { + bool: { + should: selectedCriticalities.map((value) => ({ + term: { + 'asset.criticality': value, + }, + })), + minimum_should_match: 1, + }, + }, + ] + : []; const sourceFilter: QueryDslQueryContainer[] = selectedSources.map((value) => ({ term: { @@ -38,23 +47,27 @@ export const useEntitiesListFilters = ({ }, })); - const severityFilter: QueryDslQueryContainer[] = selectedSeverities.map((value) => ({ - bool: { - should: [ + const severityFilter: QueryDslQueryContainer[] = selectedSeverities.length + ? [ { - term: { - 'host.risk.calculated_level': value, + bool: { + should: selectedSeverities.flatMap((value) => [ + { + term: { + 'host.risk.calculated_level': value, + }, + }, + { + term: { + 'user.risk.calculated_level': value, + }, + }, + ]), + minimum_should_match: 1, }, }, - { - term: { - 'user.risk.calculated_level': value, - }, - }, - ], - minimum_should_match: 1, - }, - })); + ] + : []; const filterList: QueryDslQueryContainer[] = [ ...severityFilter, diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts new file mode 100644 index 0000000000000..ef6ccd5d6fe20 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UseQueryOptions } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import type { ListEntityEnginesResponse } from '../../../../../common/api/entity_analytics'; +import { useEntityStoreRoutes } from '../../../api/entity_store'; + +export const ENTITY_STORE_ENGINE_STATUS = 'ENTITY_STORE_ENGINE_STATUS'; + +interface Options { + disabled?: boolean; + polling?: UseQueryOptions<ListEntityEnginesResponse>['refetchInterval']; +} + +export const useEntityEngineStatus = (opts: Options = {}) => { + // QUESTION: Maybe we should have an `EnablementStatus` API route for this? + const { listEntityEngines } = useEntityStoreRoutes(); + + const { isLoading, data } = useQuery<ListEntityEnginesResponse>({ + queryKey: [ENTITY_STORE_ENGINE_STATUS], + queryFn: () => listEntityEngines(), + refetchInterval: opts.polling, + enabled: !opts.disabled, + }); + + const status = (() => { + if (data?.count === 0) { + return 'not_installed'; + } + + if (data?.engines?.every((engine) => engine.status === 'stopped')) { + return 'stopped'; + } + + if (data?.engines?.some((engine) => engine.status === 'installing')) { + return 'installing'; + } + + if (isLoading) { + return 'loading'; + } + + if (!data) { + return 'error'; + } + + return 'enabled'; + })(); + + return { + status, + }; +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts new file mode 100644 index 0000000000000..29e9e6c5098c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCallback, useState } from 'react'; + +import type { + DeleteEntityEngineResponse, + InitEntityEngineResponse, + StopEntityEngineResponse, +} from '../../../../../common/api/entity_analytics'; +import { useEntityStoreRoutes } from '../../../api/entity_store'; +import { ENTITY_STORE_ENGINE_STATUS, useEntityEngineStatus } from './use_entity_engine_status'; + +const ENTITY_STORE_ENABLEMENT_INIT = 'ENTITY_STORE_ENABLEMENT_INIT'; + +export const useEntityStoreEnablement = () => { + const [polling, setPolling] = useState(false); + + useEntityEngineStatus({ + disabled: !polling, + polling: (data) => { + const shouldStopPolling = + data?.engines && + data.engines.length > 0 && + data.engines.every((engine) => engine.status === 'started'); + + if (shouldStopPolling) { + setPolling(false); + return false; + } + return 5000; + }, + }); + + const { initEntityStore } = useEntityStoreRoutes(); + const { refetch: initialize } = useQuery({ + queryKey: [ENTITY_STORE_ENABLEMENT_INIT], + queryFn: () => Promise.all([initEntityStore('user'), initEntityStore('host')]), + enabled: false, + }); + + const enable = useCallback(() => { + initialize().then(() => setPolling(true)); + }, [initialize]); + + return { enable }; +}; + +export const INIT_ENTITY_ENGINE_STATUS_KEY = ['POST', 'INIT_ENTITY_ENGINE']; + +export const useInvalidateEntityEngineStatusQuery = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries([ENTITY_STORE_ENGINE_STATUS], { + refetchType: 'active', + }); + }, [queryClient]); +}; + +export const useInitEntityEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); + const { initEntityStore } = useEntityStoreRoutes(); + return useMutation<InitEntityEngineResponse[]>( + () => Promise.all([initEntityStore('user'), initEntityStore('host')]), + { + ...options, + mutationKey: INIT_ENTITY_ENGINE_STATUS_KEY, + onSettled: (...args) => { + invalidateEntityEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; + +export const STOP_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; + +export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); + const { stopEntityStore } = useEntityStoreRoutes(); + return useMutation<StopEntityEngineResponse[]>( + () => Promise.all([stopEntityStore('user'), stopEntityStore('host')]), + { + ...options, + mutationKey: STOP_ENTITY_ENGINE_STATUS_KEY, + onSettled: (...args) => { + invalidateEntityEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; + +export const DELETE_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; + +export const useDeleteEntityEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); + const { deleteEntityEngine } = useEntityStoreRoutes(); + return useMutation<DeleteEntityEngineResponse[]>( + () => Promise.all([deleteEntityEngine('user'), deleteEntityEngine('host')]), + { + ...options, + mutationKey: DELETE_ENTITY_ENGINE_STATUS_KEY, + onSettled: (...args) => { + invalidateEntityEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts new file mode 100644 index 0000000000000..127ff5c88506b --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts @@ -0,0 +1,64 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ENABLE_ENTITY_STORE_TITLE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.title.store', + { + defaultMessage: 'Enable entity store', + } +); +export const ENABLE_RISK_SCORE_TITLE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.title.risk', + { + defaultMessage: 'Enable entity risk score', + } +); +export const ENABLE_ALL_TITLE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.title.both', + { + defaultMessage: 'Enable entity store and risk score', + } +); + +export const ENABLEMENT_INITIALIZING_RISK_ENGINE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.initializing.risk', + { + defaultMessage: 'Initializing risk engine', + } +); + +export const ENABLEMENT_INITIALIZING_ENTITY_STORE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.initializing.store', + { + defaultMessage: 'Initializing entity store', + } +); + +export const ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.description.risk', + { + defaultMessage: + 'Provides real-time visibility into user activity, helping you identify and mitigate potential security risks.', + } +); + +export const ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.description.store', + { + defaultMessage: "Allows comprehensive monitoring of your system's hosts and users.", + } +); + +export const ENABLEMENT_DESCRIPTION_BOTH = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.description.both', + { + defaultMessage: + 'Your entity store is currently empty. Add information about your entities directly from your logs, or import them using a text file.', + } +); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/images/entity_store_dashboard.png b/x-pack/plugins/security_solution/public/entity_analytics/images/entity_store_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..b1a6ae31fadcc4179b9539d2bacd572b950d2320 GIT binary patch literal 49832 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 OKvhZWRjq<m<o^SDX10?6 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx deleted file mode 100644 index eced6e59031ad..0000000000000 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIcon, - EuiLink, - EuiPageHeader, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, - EuiEmptyPrompt, - EuiCallOut, - EuiCode, -} from '@elastic/eui'; -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; -import { useUiSetting$, useKibana } from '../../common/lib/kibana'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../common/constants'; -import { AssetCriticalityFileUploader } from '../components/asset_criticality_file_uploader/asset_criticality_file_uploader'; -import { useAssetCriticalityPrivileges } from '../components/asset_criticality/use_asset_criticality'; -import { useHasSecurityCapability } from '../../helper_hooks'; - -export const AssetCriticalityUploadPage = () => { - const { docLinks } = useKibana().services; - const entityAnalyticsLinks = docLinks.links.securitySolution.entityAnalytics; - const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); - const { - data: privileges, - error: privilegesError, - isLoading, - } = useAssetCriticalityPrivileges('AssetCriticalityUploadPage'); - const hasWritePermissions = privileges?.has_write_permissions; - - if (isLoading) { - // Wait for permission before rendering content to avoid flickering - return null; - } - - if ( - !hasEntityAnalyticsCapability || - !isAssetCriticalityEnabled || - privilegesError?.body.status_code === 403 - ) { - const errorMessage = privilegesError?.body.message ?? ( - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage" - defaultMessage='Please enable "{ENABLE_ASSET_CRITICALITY_SETTING}" on advanced settings to access the page.' - values={{ - ENABLE_ASSET_CRITICALITY_SETTING, - }} - /> - ); - - return ( - <EuiEmptyPrompt - iconType="warning" - title={ - <h2> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle" - defaultMessage="This page is disabled" - /> - </h2> - } - body={<p>{errorMessage}</p>} - /> - ); - } - - if (!hasWritePermissions) { - return ( - <EuiCallOut - title={ - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.noPermissionTitle" - defaultMessage="Insufficient index privileges to access this page" - /> - } - color="primary" - iconType="iInCircle" - > - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.missingPermissionsCallout.description" - defaultMessage="Write permission is required for the {index} index pattern in order to access this page. Contact your administrator for further assistance." - values={{ - index: <EuiCode>{ASSET_CRITICALITY_INDEX_PATTERN}</EuiCode>, - }} - /> - </EuiText> - </EuiCallOut> - ); - } - - return ( - <> - <EuiPageHeader - data-test-subj="assetCriticalityUploadPage" - pageTitle={ - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title" - defaultMessage="Asset criticality" - /> - } - /> - <EuiHorizontalRule /> - <EuiSpacer size="l" /> - <EuiFlexGroup gutterSize="xl"> - <EuiFlexItem grow={3}> - <EuiTitle size="s"> - <h2> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle" - defaultMessage="Import your asset criticality data" - /> - </h2> - </EuiTitle> - <EuiSpacer size="m" /> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.description" - defaultMessage="Bulk assign asset criticality by importing a CSV, TXT, or TSV file exported from your asset management tools. This ensures data accuracy and reduces manual input errors." - /> - </EuiText> - <EuiSpacer size="s" /> - <AssetCriticalityFileUploader /> - </EuiFlexItem> - - <EuiFlexItem grow={2}> - <EuiPanel hasBorder={true} paddingSize="l" grow={false}> - <EuiIcon type="questionInCircle" size="xl" /> - <EuiSpacer size="m" /> - <EuiTitle size="xxs"> - <h3> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.information.title" - defaultMessage="What is asset criticality?" - /> - </h3> - </EuiTitle> - <EuiSpacer size="s" /> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.information.description" - defaultMessage="Asset criticality allows you to classify entities based on their importance and impact on business operations. Use asset criticality to guide prioritization for alert triaging, threat-hunting, and investigation activities." - /> - </EuiText> - <EuiHorizontalRule /> - <EuiTitle size="xxs"> - <h4> - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.information.usefulLinks" - defaultMessage="Useful links" - /> - </h4> - </EuiTitle> - <EuiSpacer size="xs" /> - - <EuiLink - target="_blank" - rel="noopener nofollow noreferrer" - href={entityAnalyticsLinks.assetCriticality} - > - <FormattedMessage - id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.documentationLink" - defaultMessage="Asset criticality documentation" - /> - </EuiLink> - </EuiPanel> - </EuiFlexItem> - </EuiFlexGroup> - </> - ); -}; - -AssetCriticalityUploadPage.displayName = 'AssetCriticalityUploadPage'; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx index 90f5ec66c8a38..2fbc4f67ab6ef 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx @@ -23,16 +23,16 @@ import { RiskScoreUpdatePanel } from '../components/risk_score_update_panel'; import { useHasSecurityCapability } from '../../helper_hooks'; import { EntityAnalyticsHeader } from '../components/entity_analytics_header'; import { EntityAnalyticsAnomalies } from '../components/entity_analytics_anomalies'; + +import { EntityStoreDashboardPanels } from '../components/entity_store/components/dashboard_panels'; import { EntityAnalyticsRiskScores } from '../components/entity_analytics_risk_score'; -import { EntitiesList } from '../components/entity_store/entities_list'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; const EntityAnalyticsComponent = () => { const { data: riskScoreEngineStatus } = useRiskEngineStatus(); const { indicesExist, loading: isSourcererLoading, sourcererDataView } = useSourcererDataView(); const isRiskScoreModuleLicenseAvailable = useHasSecurityCapability('entity-analytics'); - - const isEntityStoreDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); + const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); return ( <> @@ -59,23 +59,25 @@ const EntityAnalyticsComponent = () => { <EntityAnalyticsHeader /> </EuiFlexItem> - <EuiFlexItem> - <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.host} /> - </EuiFlexItem> + {!isEntityStoreFeatureFlagDisabled ? ( + <EuiFlexItem> + <EntityStoreDashboardPanels /> + </EuiFlexItem> + ) : ( + <> + <EuiFlexItem> + <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.host} /> + </EuiFlexItem> - <EuiFlexItem> - <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.user} /> - </EuiFlexItem> + <EuiFlexItem> + <EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.user} /> + </EuiFlexItem> + </> + )} <EuiFlexItem> <EntityAnalyticsAnomalies /> </EuiFlexItem> - - {!isEntityStoreDisabled ? ( - <EuiFlexItem> - <EntitiesList /> - </EuiFlexItem> - ) : null} </EuiFlexGroup> )} </SecuritySolutionPageWrapper> diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx new file mode 100644 index 0000000000000..0e09e5ceac3ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -0,0 +1,456 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiConfirmModal, + EuiHorizontalRule, + EuiIcon, + EuiLink, + EuiPageHeader, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + EuiCallOut, + EuiCode, + EuiSwitch, + EuiHealth, + EuiButton, + EuiLoadingSpinner, + EuiToolTip, + EuiBetaBadge, +} from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useEntityEngineStatus } from '../components/entity_store/hooks/use_entity_engine_status'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; +import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; +import { useUiSetting$, useKibana } from '../../common/lib/kibana'; +import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../common/constants'; +import { AssetCriticalityFileUploader } from '../components/asset_criticality_file_uploader/asset_criticality_file_uploader'; +import { useAssetCriticalityPrivileges } from '../components/asset_criticality/use_asset_criticality'; +import { useHasSecurityCapability } from '../../helper_hooks'; +import { + useDeleteEntityEngineMutation, + useInitEntityEngineMutation, + useStopEntityEngineMutation, +} from '../components/entity_store/hooks/use_entity_store'; +import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../common/translations'; + +const entityStoreEnabledStatuses = ['enabled']; +const switchDisabledStatuses = ['error', 'loading', 'installing']; +const entityStoreInstallingStatuses = ['installing', 'loading']; + +export const EntityStoreManagementPage = () => { + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); + const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); + const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); + const { + data: assetCriticalityPrivileges, + error: assetCriticalityPrivilegesError, + isLoading: assetCriticalityIsLoading, + } = useAssetCriticalityPrivileges('AssetCriticalityUploadPage'); + const hasAssetCriticalityWritePermissions = assetCriticalityPrivileges?.has_write_permissions; + + const [polling, setPolling] = useState(false); + const entityStoreStatus = useEntityEngineStatus({ + disabled: false, + polling: !polling + ? undefined + : (data) => { + const shouldStopPolling = + data?.engines && + data.engines.length > 0 && + data.engines.every((engine) => engine.status === 'started'); + + if (shouldStopPolling) { + setPolling(false); + return false; + } + return 1000; + }, + }); + const initEntityEngineMutation = useInitEntityEngineMutation(); + const stopEntityEngineMutation = useStopEntityEngineMutation(); + const deleteEntityEngineMutation = useDeleteEntityEngineMutation({ + onSuccess: () => { + closeClearModal(); + }, + }); + + const [isClearModalVisible, setIsClearModalVisible] = useState(false); + const closeClearModal = useCallback(() => setIsClearModalVisible(false), []); + const showClearModal = useCallback(() => setIsClearModalVisible(true), []); + + const onSwitchClick = useCallback(() => { + if (switchDisabledStatuses.includes(entityStoreStatus.status)) { + return; + } + + if (entityStoreEnabledStatuses.includes(entityStoreStatus.status)) { + stopEntityEngineMutation.mutate(); + } else { + setPolling(true); + initEntityEngineMutation.mutate(); + } + }, [initEntityEngineMutation, stopEntityEngineMutation, entityStoreStatus]); + + if (assetCriticalityIsLoading) { + // Wait for permission before rendering content to avoid flickering + return null; + } + + const AssetCriticalityIssueCallout: React.FC = () => { + const errorMessage = assetCriticalityPrivilegesError?.body.message ?? ( + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage" + defaultMessage='Please enable "{ENABLE_ASSET_CRITICALITY_SETTING}" in advanced settings to access this functionality.' + values={{ + ENABLE_ASSET_CRITICALITY_SETTING, + }} + /> + ); + + return ( + <EuiFlexItem grow={false}> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unavailable" + defaultMessage="Asset criticality CSV file upload functionality unavailable." + /> + } + color="primary" + iconType="iInCircle" + > + <EuiText size="s">{errorMessage}</EuiText> + </EuiCallOut> + </EuiFlexItem> + ); + }; + + const ClearEntityDataPanel: React.FC = () => { + return ( + <> + <EuiPanel + paddingSize="l" + grow={false} + color="subdued" + borderRadius="none" + hasShadow={false} + > + <EuiText size="s"> + <h3> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntityData" + defaultMessage="Clear entity data" + /> + </h3> + <EuiSpacer size="s" /> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntityData" + defaultMessage={`Remove all extracted entity data from the store. This action will + permanently delete persisted user and host records, and data will no longer be available for analysis. + Proceed with caution, as this cannot be undone. Note that this operation will not delete source data, + Entity risk scores, or Asset Criticality assignments.`} + /> + </EuiText> + <EuiSpacer size="m" /> + <EuiButton + color="danger" + iconType="trash" + onClick={() => { + showClearModal(); + }} + > + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clear" + defaultMessage="Clear" + /> + </EuiButton> + </EuiPanel> + {isClearModalVisible && ( + <EuiConfirmModal + isLoading={deleteEntityEngineMutation.isLoading} + title={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.title" + defaultMessage="Clear Entity data?" + /> + } + onCancel={closeClearModal} + onConfirm={() => { + deleteEntityEngineMutation.mutate(); + }} + cancelButtonText={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.close" + defaultMessage="Close" + /> + } + confirmButtonText={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.clearAllEntities" + defaultMessage="Clear All Entities" + /> + } + buttonColor="danger" + defaultFocusedButton="confirm" + > + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearConfirmation" + defaultMessage={ + 'This will delete all Security Entity store records. Source data, Entity risk scores, and Asset criticality assignments are unaffected by this action. This operation cannot be undone.' + } + /> + </EuiConfirmModal> + )} + </> + ); + }; + + const FileUploadSection: React.FC = () => { + if ( + !hasEntityAnalyticsCapability || + !isAssetCriticalityEnabled || + assetCriticalityPrivilegesError?.body.status_code === 403 + ) { + return <AssetCriticalityIssueCallout />; + } + if (!hasAssetCriticalityWritePermissions) { + return <InsufficientAssetCriticalityPrivilegesCallout />; + } + return ( + <EuiFlexItem grow={3}> + <EuiTitle size="s"> + <h2> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle" + defaultMessage="Import entities using a text file" + /> + </h2> + </EuiTitle> + <EuiSpacer size="m" /> + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.description" + defaultMessage="Bulk assign asset criticality by importing a CSV, TXT, or TSV file exported from your asset management tools. This ensures data accuracy and reduces manual input errors." + /> + </EuiText> + <EuiSpacer size="s" /> + <AssetCriticalityFileUploader /> + </EuiFlexItem> + ); + }; + + const canDeleteEntityEngine = !['not_installed', 'loading', 'installing'].includes( + entityStoreStatus.status + ); + + const isMutationLoading = + initEntityEngineMutation.isLoading || + stopEntityEngineMutation.isLoading || + deleteEntityEngineMutation.isLoading; + + return ( + <> + <EuiPageHeader + data-test-subj="entityStoreManagementPage" + pageTitle={ + <> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.title" + defaultMessage="Entity Store" + />{' '} + <EuiToolTip content={TECHNICAL_PREVIEW_TOOLTIP}> + <EuiBetaBadge label={TECHNICAL_PREVIEW} /> + </EuiToolTip> + </> + } + alignItems="center" + rightSideItems={ + !isEntityStoreFeatureFlagDisabled + ? [ + <EnablementButton + isLoading={ + isMutationLoading || + entityStoreInstallingStatuses.includes(entityStoreStatus.status) + } + isDisabled={ + isMutationLoading || switchDisabledStatuses.includes(entityStoreStatus.status) + } + onSwitch={onSwitchClick} + status={entityStoreStatus.status} + />, + ] + : [] + } + /> + <EuiSpacer size="s" /> + <EuiText> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.subTitle" + defaultMessage="Allows comprehensive monitoring of your system's hosts and users." + /> + </EuiText> + {isEntityStoreFeatureFlagDisabled && <EntityStoreFeatureFlagNotAvailableCallout />} + <EuiHorizontalRule /> + <EuiSpacer size="l" /> + <EuiFlexGroup gutterSize="xl"> + <FileUploadSection /> + <EuiFlexItem grow={2}> + <EuiFlexGroup direction="column"> + <WhatIsAssetCriticalityPanel /> + {!isEntityStoreFeatureFlagDisabled && canDeleteEntityEngine && <ClearEntityDataPanel />} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </> + ); +}; + +EntityStoreManagementPage.displayName = 'EntityStoreManagementPage'; + +const WhatIsAssetCriticalityPanel: React.FC = () => { + const { docLinks } = useKibana().services; + const entityAnalyticsLinks = docLinks.links.securitySolution.entityAnalytics; + + return ( + <EuiPanel hasBorder={true} paddingSize="l" grow={false}> + <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiIcon type="questionInCircle" size="xl" /> + <EuiTitle size="xxs"> + <h3> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.information.title" + defaultMessage="What is asset criticality?" + /> + </h3> + </EuiTitle> + </EuiFlexGroup> + <EuiSpacer size="s" /> + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.information.description" + defaultMessage="Asset criticality allows you to classify entities based on their importance and impact on business operations. Use asset criticality to guide prioritization for alert triaging, threat-hunting, and investigation activities." + /> + </EuiText> + <EuiHorizontalRule /> + <EuiTitle size="xxs"> + <h4> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.information.usefulLinks" + defaultMessage="Useful links" + /> + </h4> + </EuiTitle> + <EuiSpacer size="xs" /> + + <EuiLink + target="_blank" + rel="noopener nofollow noreferrer" + href={entityAnalyticsLinks.assetCriticality} + > + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.documentationLink" + defaultMessage="Asset criticality documentation" + /> + </EuiLink> + </EuiPanel> + ); +}; + +const EntityStoreFeatureFlagNotAvailableCallout: React.FC = () => { + return ( + <> + <EuiSpacer size="m" /> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.entityStoreManagementPage.featureFlagDisabled" + defaultMessage="Entity Store capabilities not available" + /> + } + color="primary" + iconType="iInCircle" + > + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.entityStoreManagementPage.featureFlagDisabledDescription" + defaultMessage="The full capabilities of the Entity Store have been disabled in this environment. Contact your administrator for further assistance." + /> + </EuiText> + </EuiCallOut> + </> + ); +}; + +const EntityStoreHealth: React.FC<{ currentEntityStoreStatus: string }> = ({ + currentEntityStoreStatus, +}) => { + return ( + <EuiHealth + textSize="m" + color={entityStoreEnabledStatuses.includes(currentEntityStoreStatus) ? 'success' : 'subdued'} + > + {entityStoreEnabledStatuses.includes(currentEntityStoreStatus) ? 'On' : 'Off'} + </EuiHealth> + ); +}; + +const EnablementButton: React.FC<{ + isLoading: boolean; + isDisabled: boolean; + status: string; + onSwitch: () => void; +}> = ({ isLoading, isDisabled, status, onSwitch }) => { + return ( + <EuiFlexGroup alignItems="center"> + {isLoading && ( + <EuiFlexItem> + <EuiLoadingSpinner data-test-subj="entity-store-status-loading" size="m" /> + </EuiFlexItem> + )} + <EntityStoreHealth currentEntityStoreStatus={status} /> + <EuiSwitch + showLabel={false} + label="" + onChange={onSwitch} + data-test-subj="entity-store-switch" + checked={entityStoreEnabledStatuses.includes(status)} + disabled={isDisabled} + /> + </EuiFlexGroup> + ); +}; + +const InsufficientAssetCriticalityPrivilegesCallout: React.FC = () => { + return ( + <EuiCallOut + title={ + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.noPermissionTitle" + defaultMessage="Insufficient index privileges to perform CSV upload" + /> + } + color="primary" + iconType="iInCircle" + > + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.missingPermissionsCallout.description" + defaultMessage="Write permission is required for the {index} index pattern in order to access this functionality. Contact your administrator for further assistance." + values={{ + index: <EuiCode>{ASSET_CRITICALITY_INDEX_PATTERN}</EuiCode>, + }} + /> + </EuiText> + </EuiCallOut> + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx index 835265c7402fe..1cc1a24b020cb 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx @@ -14,12 +14,13 @@ import { NotFoundPage } from '../app/404'; import { ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, + ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, ENTITY_ANALYTICS_MANAGEMENT_PATH, SecurityPageName, } from '../../common/constants'; import { EntityAnalyticsManagementPage } from './pages/entity_analytics_management_page'; import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; -import { AssetCriticalityUploadPage } from './pages/asset_criticality_upload_page'; +import { EntityStoreManagementPage } from './pages/entity_store_management_page'; const EntityAnalyticsManagementTelemetry = () => ( <PluginTemplateWrapper> @@ -47,7 +48,7 @@ EntityAnalyticsManagementContainer.displayName = 'EntityAnalyticsManagementConta const EntityAnalyticsAssetClassificationTelemetry = () => ( <PluginTemplateWrapper> <TrackApplicationView viewId={SecurityPageName.entityAnalyticsAssetClassification}> - <AssetCriticalityUploadPage /> + <EntityStoreManagementPage /> <SpyRoute pageName={SecurityPageName.entityAnalyticsAssetClassification} /> </TrackApplicationView> </PluginTemplateWrapper> @@ -69,6 +70,30 @@ const EntityAnalyticsAssetClassificationContainer: React.FC = React.memo(() => { EntityAnalyticsAssetClassificationContainer.displayName = 'EntityAnalyticsAssetClassificationContainer'; +const EntityAnalyticsEntityStoreTelemetry = () => ( + <PluginTemplateWrapper> + <TrackApplicationView viewId={SecurityPageName.entityAnalyticsEntityStoreManagement}> + <EntityStoreManagementPage /> + <SpyRoute pageName={SecurityPageName.entityAnalyticsEntityStoreManagement} /> + </TrackApplicationView> + </PluginTemplateWrapper> +); + +const EntityAnalyticsEntityStoreContainer: React.FC = React.memo(() => { + return ( + <Switch> + <Route + path={ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH} + exact + component={EntityAnalyticsEntityStoreTelemetry} + /> + <Route component={NotFoundPage} /> + </Switch> + ); +}); + +EntityAnalyticsEntityStoreContainer.displayName = 'EntityAnalyticsEntityStoreContainer'; + export const routes = [ { path: ENTITY_ANALYTICS_MANAGEMENT_PATH, @@ -78,4 +103,8 @@ export const routes = [ path: ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, component: EntityAnalyticsAssetClassificationContainer, }, + { + path: ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, + component: EntityAnalyticsEntityStoreContainer, + }, ]; diff --git a/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx b/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx index 09019820ab548..82cde8a24f153 100644 --- a/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx @@ -138,7 +138,7 @@ export interface Columns<T, U = T> { name: string | React.ReactNode; render?: (item: T, node: U) => React.ReactNode; sortable?: boolean | Func<T>; - truncateText?: boolean; + truncateText?: boolean | { lines: number }; width?: string; } diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index bc64f26a768a6..c83a7360910fa 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -15,9 +15,8 @@ import { } from '../../common/endpoint/service/authz'; import { BLOCKLIST_PATH, - ENABLE_ASSET_CRITICALITY_SETTING, ENDPOINTS_PATH, - ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, + ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, ENTITY_ANALYTICS_MANAGEMENT_PATH, EVENT_FILTERS_PATH, HOST_ISOLATION_EXCEPTIONS_PATH, @@ -39,8 +38,8 @@ import { RESPONSE_ACTIONS_HISTORY, TRUSTED_APPLICATIONS, ENTITY_ANALYTICS_RISK_SCORE, - ASSET_CRITICALITY, NOTES, + ENTITY_STORE, } from '../app/translations'; import { licenseService } from '../common/hooks/use_license'; import type { LinkItem } from '../common/links/types'; @@ -64,7 +63,7 @@ const categories = [ }), linkIds: [ SecurityPageName.entityAnalyticsManagement, - SecurityPageName.entityAnalyticsAssetClassification, + SecurityPageName.entityAnalyticsEntityStoreManagement, ], }, { @@ -196,20 +195,16 @@ export const links: LinkItem = { licenseType: 'platinum', }, { - id: SecurityPageName.entityAnalyticsAssetClassification, - title: ASSET_CRITICALITY, - description: i18n.translate( - 'xpack.securitySolution.appLinks.assetClassificationDescription', - { - defaultMessage: 'Represents the criticality of an asset to your business infrastructure.', - } - ), + id: SecurityPageName.entityAnalyticsEntityStoreManagement, + title: ENTITY_STORE, + description: i18n.translate('xpack.securitySolution.appLinks.entityStoreDescription', { + defaultMessage: "Allows comprehensive monitoring of your system's hosts and users.", + }), landingIcon: IconAssetCriticality, - path: ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, + path: ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, skipUrlState: true, hideTimeline: true, capabilities: [`${SERVER_APP_ID}.entity-analytics`], - uiSettingRequired: ENABLE_ASSET_CRITICALITY_SETTING, }, { id: SecurityPageName.responseActionsHistory, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts index db0f48877a73c..addf432f20398 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts @@ -5,12 +5,7 @@ * 2.0. */ -import type { EngineStatus } from '../../../../common/api/entity_analytics/entity_store/common.gen'; - -/** - * Default index pattern for entity store - * This is the same as the default index pattern for the SIEM app but might diverge in the future - */ +import type { EngineStatus } from '../../../../common/api/entity_analytics'; export const DEFAULT_LOOKBACK_PERIOD = '24h'; @@ -21,6 +16,7 @@ export const ENGINE_STATUS: Record<Uppercase<EngineStatus>, EngineStatus> = { STARTED: 'started', STOPPED: 'stopped', UPDATING: 'updating', + ERROR: 'error', }; export const MAX_SEARCH_RESPONSE_SIZE = 10_000; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index a71be61781e00..d2e21a1d10903 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -16,17 +16,15 @@ import type { SortOrder } from '@elastic/elasticsearch/lib/api/types'; import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import { isEqual } from 'lodash/fp'; -import type { EngineDataviewUpdateResult } from '../../../../common/api/entity_analytics/entity_store/engine/apply_dataview_indices.gen'; import type { AppClient } from '../../..'; -import type { Entity } from '../../../../common/api/entity_analytics/entity_store/entities/common.gen'; import type { + Entity, + EngineDataviewUpdateResult, InitEntityEngineRequestBody, InitEntityEngineResponse, -} from '../../../../common/api/entity_analytics/entity_store/engine/init.gen'; -import type { EntityType, InspectQuery, -} from '../../../../common/api/entity_analytics/entity_store/common.gen'; +} from '../../../../common/api/entity_analytics'; import { EngineDescriptorClient } from './saved_object/engine_descriptor'; import { ENGINE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; import { AssetCriticalityEcsMigrationClient } from '../asset_criticality/asset_criticality_migration_client'; @@ -120,7 +118,7 @@ export class EntityStoreDataClient { throw new Error('Task Manager is not available'); } - const { logger, esClient, namespace, taskManager, appClient, dataViewsService } = this.options; + const { logger } = this.options; await this.riskScoreDataClient.createRiskScoreLatestIndex(); @@ -135,8 +133,6 @@ export class EntityStoreDataClient { logger.info( `In namespace ${this.options.namespace}: Initializing entity store for ${entityType}` ); - const debugLog = (message: string) => - logger.debug(`[Entity Engine] [${entityType}] ${message}`); const descriptor = await this.engineClient.init(entityType, { filter, @@ -144,9 +140,34 @@ export class EntityStoreDataClient { indexPattern, }); logger.debug(`Initialized engine for ${entityType}`); - const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); // first create the entity definition without starting it // so that the index template is created which we can add a component template to + + this.asyncSetup( + entityType, + fieldHistoryLength, + this.options.taskManager, + indexPattern, + filter, + pipelineDebugMode + ).catch((error) => { + logger.error('There was an error during async setup of the Entity Store', error); + }); + + return descriptor; + } + + private async asyncSetup( + entityType: EntityType, + fieldHistoryLength: number, + taskManager: TaskManagerStartContract, + indexPattern: string, + filter: string, + pipelineDebugMode: boolean + ) { + const { esClient, logger, namespace, appClient, dataViewsService } = this.options; + const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); + const unitedDefinition = getUnitedEntityDefinition({ indexPatterns, entityType, @@ -155,66 +176,84 @@ export class EntityStoreDataClient { }); const { entityManagerDefinition } = unitedDefinition; - await this.entityClient.createEntityDefinition({ - definition: { - ...entityManagerDefinition, - filter, - indexPatterns: indexPattern - ? [...entityManagerDefinition.indexPatterns, ...indexPattern.split(',')] - : entityManagerDefinition.indexPatterns, - }, - installOnly: true, - }); - debugLog(`Created entity definition`); + const debugLog = (message: string) => + logger.debug(`[Entity Engine] [${entityType}] ${message}`); - // the index must be in place with the correct mapping before the enrich policy is created - // this is because the enrich policy will fail if the index does not exist with the correct fields - await createEntityIndexComponentTemplate({ - unitedDefinition, - esClient, - }); - debugLog(`Created entity index component template`); - await createEntityIndex({ - entityType, - esClient, - namespace, - logger, - }); - debugLog(`Created entity index`); + try { + // clean up any existing entity store + await this.delete(entityType, taskManager, { deleteData: false, deleteEngine: false }); + + // set up the entity manager definition + await this.entityClient.createEntityDefinition({ + definition: { + ...entityManagerDefinition, + filter, + indexPatterns: indexPattern + ? [...entityManagerDefinition.indexPatterns, ...indexPattern.split(',')] + : entityManagerDefinition.indexPatterns, + }, + installOnly: true, + }); + debugLog(`Created entity definition`); - // we must create and execute the enrich policy before the pipeline is created - // this is because the pipeline will fail if the enrich index does not exist - await createFieldRetentionEnrichPolicy({ - unitedDefinition, - esClient, - }); - debugLog(`Created field retention enrich policy`); - await executeFieldRetentionEnrichPolicy({ - unitedDefinition, - esClient, - logger, - }); - debugLog(`Executed field retention enrich policy`); - await createPlatformPipeline({ - debugMode: pipelineDebugMode, - unitedDefinition, - logger, - esClient, - }); - debugLog(`Created @platform pipeline`); + // the index must be in place with the correct mapping before the enrich policy is created + // this is because the enrich policy will fail if the index does not exist with the correct fields + await createEntityIndexComponentTemplate({ + unitedDefinition, + esClient, + }); + debugLog(`Created entity index component template`); + await createEntityIndex({ + entityType, + esClient, + namespace, + logger, + }); + debugLog(`Created entity index`); + + // we must create and execute the enrich policy before the pipeline is created + // this is because the pipeline will fail if the enrich index does not exist + await createFieldRetentionEnrichPolicy({ + unitedDefinition, + esClient, + }); + debugLog(`Created field retention enrich policy`); + await executeFieldRetentionEnrichPolicy({ + unitedDefinition, + esClient, + logger, + }); + debugLog(`Executed field retention enrich policy`); + await createPlatformPipeline({ + debugMode: pipelineDebugMode, + unitedDefinition, + logger, + esClient, + }); + debugLog(`Created @platform pipeline`); - // finally start the entity definition now that everything is in place - const updated = await this.start(entityType, { force: true }); - debugLog(`Started entity definition`); + // finally start the entity definition now that everything is in place + const updated = await this.start(entityType, { force: true }); + debugLog(`Started entity definition`); - // the task will execute the enrich policy on a schedule - await startEntityStoreFieldRetentionEnrichTask({ - namespace, - logger, - taskManager, - }); - logger.info(`Entity store initialized`); - return { ...descriptor, ...updated }; + // the task will execute the enrich policy on a schedule + await startEntityStoreFieldRetentionEnrichTask({ + namespace, + logger, + taskManager, + }); + logger.info(`Entity store initialized`); + + return updated; + } catch (err) { + this.options.logger.error( + `Error initializing entity store for ${entityType}: ${err.message}` + ); + + await this.engineClient.update(entityType, ENGINE_STATUS.ERROR); + + await this.delete(entityType, taskManager, { deleteData: true, deleteEngine: false }); + } } public async getExistingEntityDefinition(entityType: EntityType) { @@ -284,9 +323,10 @@ export class EntityStoreDataClient { public async delete( entityType: EntityType, taskManager: TaskManagerStartContract, - deleteData: boolean + options = { deleteData: false, deleteEngine: true } ) { const { namespace, logger, esClient, appClient, dataViewsService } = this.options; + const { deleteData, deleteEngine } = options; const descriptor = await this.engineClient.maybeGet(entityType); const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); const unitedDefinition = getUnitedEntityDefinition({ @@ -328,6 +368,10 @@ export class EntityStoreDataClient { logger, }); } + + if (descriptor && deleteEngine) { + await this.engineClient.delete(entityType); + } // if the last engine then stop the task const { engines } = await this.engineClient.list(); if (engines.length === 0) { @@ -338,10 +382,6 @@ export class EntityStoreDataClient { }); } - if (descriptor) { - await this.engineClient.delete(entityType); - } - return { deleted: true }; } catch (e) { logger.error(`Error deleting entity store for ${entityType}: ${e.message}`); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts index 0828f94852cf2..e11c9d3fa7b9d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts @@ -56,7 +56,10 @@ export const deleteEntityEngineRoute = ( const secSol = await context.securitySolution; const body = await secSol .getEntityStoreDataClient() - .delete(request.params.entityType, taskManager, !!request.query.data); + .delete(request.params.entityType, taskManager, { + deleteData: !!request.query.data, + deleteEngine: true, + }); return response.ok({ body }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts index c3376fe0b3c67..af7b4ba80dde5 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts @@ -41,8 +41,28 @@ export class EngineDescriptorClient { ) { const engineDescriptor = await this.find(entityType); - if (engineDescriptor.total > 0) - throw new Error(`Entity engine for ${entityType} already exists`); + if (engineDescriptor.total > 1) { + throw new Error(`Found multiple engine descriptors for entity type ${entityType}`); + } + + if (engineDescriptor.total === 1) { + const old = engineDescriptor.saved_objects[0].attributes; + const update = { + ...old, + status: ENGINE_STATUS.INSTALLING, + filter, + fieldHistoryLength, + indexPattern, + }; + await this.deps.soClient.update<EngineDescriptor>( + entityEngineDescriptorTypeName, + this.getSavedObjectId(entityType), + update, + { refresh: 'wait_for' } + ); + + return update; + } const { attributes } = await this.deps.soClient.create<EngineDescriptor>( entityEngineDescriptorTypeName, diff --git a/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts b/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts index bab25b3966c6b..9e08345dac8f1 100644 --- a/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts +++ b/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts @@ -83,7 +83,7 @@ const stackManagementLinks: Array<NodeDefinition<AppDeepLinkId, string, string>> { link: 'management:watcher' }, { link: 'management:maintenanceWindows' }, { link: `${SECURITY_UI_APP_ID}:${SecurityPageName.entityAnalyticsManagement}` }, - { link: `${SECURITY_UI_APP_ID}:${SecurityPageName.entityAnalyticsAssetClassification}` }, + { link: `${SECURITY_UI_APP_ID}:${SecurityPageName.entityAnalyticsEntityStoreManagement}` }, ], }, { diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts index 6306933d6a14b..cb39ae7c661e0 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts @@ -16,7 +16,7 @@ const SecurityManagementCards = new Map<string, CardNavExtensionDefinition['cate [ExternalPageName.visualize, 'content'], [ExternalPageName.maps, 'content'], [SecurityPageName.entityAnalyticsManagement, 'alerts'], - [SecurityPageName.entityAnalyticsAssetClassification, 'alerts'], + [SecurityPageName.entityAnalyticsEntityStoreManagement, 'alerts'], ]); export const enableManagementCardsLanding = (services: Services) => { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c9713d7d10c73..99e52c8d22234 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -35103,7 +35103,6 @@ "xpack.securitySolution.api.riskEngine.taskManagerUnavailable": "Le gestionnaire des tâches n'est pas disponible mais est requis par le moteur de risque. Veuillez autoriser le plug-in du gestionnaire des tâches et essayer à nouveau.", "xpack.securitySolution.appLinks.actionHistoryDescription": "Affichez l'historique des actions de réponse effectuées sur les hôtes.", "xpack.securitySolution.appLinks.alerts": "Alertes", - "xpack.securitySolution.appLinks.assetClassificationDescription": "Représente la criticité d'un actif pour l'infrastructure de votre entreprise.", "xpack.securitySolution.appLinks.attackDiscovery": "Attack discovery", "xpack.securitySolution.appLinks.blocklistDescription": "Excluez les applications non souhaitées de l'exécution sur vos hôtes.", "xpack.securitySolution.appLinks.category.cloudSecurity": "Sécurité du cloud", @@ -38361,7 +38360,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityResultStep.uploadAnotherFile": "Charger un autre fichier", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.acceptedFileFormats": "Formats de fichiers : {formats}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage": "Veuillez autoriser \"{ENABLE_ASSET_CRITICALITY_SETTING}\" dans les paramètres avancés pour accéder à la page.", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle": "Cette page est désactivée", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetCriticalityLabels": "Niveau de criticité : Spécifiez n'importe laquelle de ces {labels}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetIdentifierDescription": "Identificateur : Spécifiez le {hostName} ou le {userName} de l'entité.", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetTypeDescription": "Type d'entité : Veuillez indiquer si l'entité est un {host} ou un {user}.", @@ -38381,7 +38379,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "Résultats", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "Sélectionner un fichier", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "Importez vos données de criticité des ressources", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title": "Criticité des ressources", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "Format de fichier sélectionné non valide. Veuillez choisir un fichier {supportedFileExtensions} et réessayer", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "La taille maximale de fichier est de : {maxFileSize}", "xpack.securitySolution.entityAnalytics.assetCriticalityValidationStep.assignButtonText": "Affecter", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d123d0edd8948..032f15409355c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34848,7 +34848,6 @@ "xpack.securitySolution.api.riskEngine.taskManagerUnavailable": "タスクマネージャーは使用できませんが、リスクエンジンには必要です。taskManagerプラグインを有効にして、再試行してください。", "xpack.securitySolution.appLinks.actionHistoryDescription": "ホストで実行された対応アクションの履歴を表示します。", "xpack.securitySolution.appLinks.alerts": "アラート", - "xpack.securitySolution.appLinks.assetClassificationDescription": "ビジネスインフラに対するアセットの重要度を表します。", "xpack.securitySolution.appLinks.attackDiscovery": "Attack discovery", "xpack.securitySolution.appLinks.blocklistDescription": "不要なアプリケーションがホストで実行されないようにします。", "xpack.securitySolution.appLinks.category.cloudSecurity": "クラウドセキュリティ", @@ -38103,7 +38102,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityResultStep.uploadAnotherFile": "別のファイルをアップロード", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.acceptedFileFormats": "ファイル形式:{formats}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage": "ページにアクセスするには、詳細設定で\"{ENABLE_ASSET_CRITICALITY_SETTING}\"を有効化してください。", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle": "このページは無効です", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetCriticalityLabels": "重要度レベル:{labels}のいずれかを指定", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetIdentifierDescription": "識別子:エンティティの{hostName}または{userName}を指定します。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetTypeDescription": "エンティティタイプ:エンティティが{host}か{user}かを示します。", @@ -38123,7 +38121,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "結果", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "ファイルを選択", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "アセット重要度データをインポート", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title": "アセット重要度", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "無効なファイル形式が選択されました。{supportedFileExtensions}ファイルを選択して再試行してください。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "最大ファイルサイズ:{maxFileSize}", "xpack.securitySolution.entityAnalytics.assetCriticalityValidationStep.assignButtonText": "割り当て", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3e658947b010b..ea9606d1c6e00 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34891,7 +34891,6 @@ "xpack.securitySolution.api.riskEngine.taskManagerUnavailable": "任务管理器不可用,但风险引擎需要该管理器。请启用任务管理器插件然后重试。", "xpack.securitySolution.appLinks.actionHistoryDescription": "查看在主机上执行的响应操作的历史记录。", "xpack.securitySolution.appLinks.alerts": "告警", - "xpack.securitySolution.appLinks.assetClassificationDescription": "表示资产对您的业务基础设施的关键度。", "xpack.securitySolution.appLinks.attackDiscovery": "Attack Discovery", "xpack.securitySolution.appLinks.blocklistDescription": "阻止不需要的应用程序在您的主机上运行。", "xpack.securitySolution.appLinks.category.cloudSecurity": "云安全", @@ -38149,7 +38148,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityResultStep.uploadAnotherFile": "上传另一个文件", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.acceptedFileFormats": "文件格式:{formats}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage": "请在高级设置上启用“{ENABLE_ASSET_CRITICALITY_SETTING}”以访问此页面。", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle": "已禁用此页面", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetCriticalityLabels": "关键度级别:指定任意 {labels}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetIdentifierDescription": "标识符:指定实体的 {hostName} 或 {userName}。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetTypeDescription": "实体类型:指示实体是 {host} 还是 {user}。", @@ -38169,7 +38167,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "结果", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "选择文件", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "导入资产关键度数据", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title": "资产关键度", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "选定的文件格式无效。请选择 {supportedFileExtensions} 文件,然后重试", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "最大文件大小:{maxFileSize}", "xpack.securitySolution.entityAnalytics.assetCriticalityValidationStep.assignButtonText": "分配", diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts index 097e2541f57f9..1a48a7835f195 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts @@ -31,7 +31,7 @@ describe( }); it('renders page as expected', () => { - cy.get(PAGE_TITLE).should('have.text', 'Asset criticality'); + cy.get(PAGE_TITLE).should('include.text', 'Entity Store'); }); it('uploads a file', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts index ebff5204d4981..5ffffadc798f4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts @@ -18,25 +18,38 @@ import { RiskScoreEntity } from '../../../tasks/risk_scores/common'; import { ENTITY_ANALYTICS_URL } from '../../../urls/navigation'; import { PAGE_TITLE } from '../../../screens/entity_analytics_management'; -describe('Enable risk scores from dashboard', { tags: ['@ess', '@serverless'] }, () => { - beforeEach(() => { - login(); - visit(ENTITY_ANALYTICS_URL); - }); - - it('host risk enable button should redirect to entity management page', () => { - cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('exist'); - - clickEnableRiskScore(RiskScoreEntity.host); - - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); - - it('user risk enable button should redirect to entity management page', () => { - cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('exist'); - - clickEnableRiskScore(RiskScoreEntity.user); - - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); -}); +describe( + 'Enable risk scores from dashboard', + { + tags: ['@ess', '@serverless'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify(['entityStoreDisabled'])}`, + ], + }, + }, + }, + () => { + beforeEach(() => { + login(); + visit(ENTITY_ANALYTICS_URL); + }); + + it('host risk enable button should redirect to entity management page', () => { + cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('exist'); + + clickEnableRiskScore(RiskScoreEntity.host); + + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); + }); + + it('user risk enable button should redirect to entity management page', () => { + cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('exist'); + + clickEnableRiskScore(RiskScoreEntity.user); + + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts index ee229539c8dbd..ef114aec912a6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts @@ -34,68 +34,81 @@ import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; const spaceId = 'default'; -describe('Upgrade risk scores', { tags: ['@ess'] }, () => { - beforeEach(() => { - login(); - deleteRiskEngineConfiguration(); - deleteAlertsAndRules(); - }); - - describe('show upgrade risk button', () => { +describe( + 'Upgrade risk scores', + { + tags: ['@ess'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify(['entityStoreDisabled'])}`, + ], + }, + }, + }, + () => { beforeEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); - installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId); - installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId); - visitWithTimeRange(ENTITY_ANALYTICS_URL); - }); - - afterEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); - cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); - cy.task('esArchiverUnload', { archiveName: 'risk_users' }); - }); - - it('shows upgrade panel', () => { - cy.get(UPGRADE_RISK_SCORE_BUTTON).should('be.visible'); - - clickUpgradeRiskScore(); - - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); - }); - - describe('upgrade risk engine', () => { - beforeEach(() => { - cy.task('esArchiverLoad', { archiveName: 'risk_hosts' }); - cy.task('esArchiverLoad', { archiveName: 'risk_users' }); login(); - installRiskScoreModule(); - visitWithTimeRange(ENTITY_ANALYTICS_URL); - }); - - afterEach(() => { - cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); - cy.task('esArchiverUnload', { archiveName: 'risk_users' }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); deleteRiskEngineConfiguration(); + deleteAlertsAndRules(); }); - it('show old risk score data before upgrade, and hide after', () => { - cy.get(HOSTS_TABLE).should('be.visible'); - cy.get(HOSTS_TABLE_ROWS).should('have.length', 5); - - cy.get(USERS_TABLE).should('be.visible'); - cy.get(USERS_TABLE_ROWS).should('have.length', 5); - - upgradeRiskEngine(); - - visitWithTimeRange(ENTITY_ANALYTICS_URL); + describe('show upgrade risk button', () => { + beforeEach(() => { + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId); + installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId); + visitWithTimeRange(ENTITY_ANALYTICS_URL); + }); + + afterEach(() => { + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); + cy.task('esArchiverUnload', { archiveName: 'risk_users' }); + }); + + it('shows upgrade panel', () => { + cy.get(UPGRADE_RISK_SCORE_BUTTON).should('be.visible'); + + clickUpgradeRiskScore(); + + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); + }); + }); - cy.get(HOST_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); - cy.get(USER_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); + describe('upgrade risk engine', () => { + beforeEach(() => { + cy.task('esArchiverLoad', { archiveName: 'risk_hosts' }); + cy.task('esArchiverLoad', { archiveName: 'risk_users' }); + login(); + installRiskScoreModule(); + visitWithTimeRange(ENTITY_ANALYTICS_URL); + }); + + afterEach(() => { + cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); + cy.task('esArchiverUnload', { archiveName: 'risk_users' }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + deleteRiskEngineConfiguration(); + }); + + it('show old risk score data before upgrade, and hide after', () => { + cy.get(HOSTS_TABLE).should('be.visible'); + cy.get(HOSTS_TABLE_ROWS).should('have.length', 5); + + cy.get(USERS_TABLE).should('be.visible'); + cy.get(USERS_TABLE_ROWS).should('have.length', 5); + + upgradeRiskEngine(); + + visitWithTimeRange(ENTITY_ANALYTICS_URL); + + cy.get(HOST_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); + cy.get(USER_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts b/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts index 6af6826227b6c..a397716d0c503 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts @@ -7,7 +7,7 @@ import { getDataTestSubjectSelector } from '../helpers/common'; -export const PAGE_TITLE = getDataTestSubjectSelector('assetCriticalityUploadPage'); +export const PAGE_TITLE = getDataTestSubjectSelector('entityStoreManagementPage'); export const FILE_PICKER = getDataTestSubjectSelector('asset-criticality-file-picker'); export const ASSIGN_BUTTON = getDataTestSubjectSelector('asset-criticality-assign-button'); export const RESULT_STEP = getDataTestSubjectSelector('asset-criticality-result-step-success'); From 10364fba2db8bb2080a97173c76a9d1aef1e80ed Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:45:03 +0200 Subject: [PATCH 45/84] [ES|QL] More AST mutation APIs (#196240) ## Summary Partially addresses https://github.com/elastic/kibana/issues/191812 Implements the following high-level ES|QL AST manipulation methods: - `.generic` - `.appendCommandArgument()` — Add a new main command argument to a command. - `.removeCommandArgument()` — Remove a command argument from the AST. - `.commands` - `.from` - `.sources` - `.list()` — List all `FROM` sources. - `.find()` — Find a source by name. - `.remove()` — Remove a source by name. - `.insert()` — Insert a source. - `.upsert()` — Insert a source, if it does not exist. - `.limit` - `.list()` — List all `LIMIT` commands. - `.byIndex()` — Find a `LIMIT` command by index. - `.find()` — Find a `LIMIT` command by a predicate function. - `.remove()` — Remove a `LIMIT` command by index. - `.set()` — Set the limit value of a specific `LIMIT` command. - `.upsert()` — Insert a `LIMIT` command, or update the limit value if it already exists. ### Checklist Delete any items that are not applicable to this PR. - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) --- packages/kbn-esql-ast/src/ast/util.ts | 14 + packages/kbn-esql-ast/src/builder/builder.ts | 17 + packages/kbn-esql-ast/src/mutate/README.md | 42 ++- .../src/mutate/commands/from/index.ts | 3 +- .../src/mutate/commands/from/metadata.ts | 2 +- .../src/mutate/commands/from/sources.test.ts | 246 ++++++++++++++ .../src/mutate/commands/from/sources.ts | 111 +++++++ .../kbn-esql-ast/src/mutate/commands/index.ts | 3 +- .../src/mutate/commands/limit/index.test.ts | 311 ++++++++++++++++++ .../src/mutate/commands/limit/index.ts | 134 ++++++++ .../kbn-esql-ast/src/mutate/generic.test.ts | 40 +++ packages/kbn-esql-ast/src/mutate/generic.ts | 93 +++++- 12 files changed, 1003 insertions(+), 13 deletions(-) create mode 100644 packages/kbn-esql-ast/src/ast/util.ts create mode 100644 packages/kbn-esql-ast/src/mutate/commands/from/sources.test.ts create mode 100644 packages/kbn-esql-ast/src/mutate/commands/from/sources.ts create mode 100644 packages/kbn-esql-ast/src/mutate/commands/limit/index.test.ts create mode 100644 packages/kbn-esql-ast/src/mutate/commands/limit/index.ts diff --git a/packages/kbn-esql-ast/src/ast/util.ts b/packages/kbn-esql-ast/src/ast/util.ts new file mode 100644 index 0000000000000..0cd94aba85cf1 --- /dev/null +++ b/packages/kbn-esql-ast/src/ast/util.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ESQLAstNode, ESQLCommandOption } from '../types'; + +export const isOptionNode = (node: ESQLAstNode): node is ESQLCommandOption => { + return !!node && typeof node === 'object' && !Array.isArray(node) && node.type === 'option'; +}; diff --git a/packages/kbn-esql-ast/src/builder/builder.ts b/packages/kbn-esql-ast/src/builder/builder.ts index ece92fbcd7d5e..26b64a6312ee4 100644 --- a/packages/kbn-esql-ast/src/builder/builder.ts +++ b/packages/kbn-esql-ast/src/builder/builder.ts @@ -100,6 +100,23 @@ export namespace Builder { }; }; + export const indexSource = ( + index: string, + cluster?: string, + template?: Omit<AstNodeTemplate<ESQLSource>, 'name' | 'index' | 'cluster'>, + fromParser?: Partial<AstNodeParserFields> + ): ESQLSource => { + return { + ...template, + ...Builder.parserFields(fromParser), + index, + cluster, + name: (cluster ? cluster + ':' : '') + index, + sourceType: 'index', + type: 'source', + }; + }; + export const column = ( template: Omit<AstNodeTemplate<ESQLColumn>, 'name' | 'quoted'>, fromParser?: Partial<AstNodeParserFields> diff --git a/packages/kbn-esql-ast/src/mutate/README.md b/packages/kbn-esql-ast/src/mutate/README.md index 8c38bb72ca226..7dfd3d77a1395 100644 --- a/packages/kbn-esql-ast/src/mutate/README.md +++ b/packages/kbn-esql-ast/src/mutate/README.md @@ -26,11 +26,37 @@ console.log(src); // FROM index METADATA _lang, _id ## API -- `.commands.from.metadata.list()` — List all `METADATA` fields. -- `.commands.from.metadata.find()` — Find a `METADATA` field by name. -- `.commands.from.metadata.removeByPredicate()` — Remove a `METADATA` - field by matching a predicate. -- `.commands.from.metadata.remove()` — Remove a `METADATA` field by name. -- `.commands.from.metadata.insert()` — Insert a `METADATA` field. -- `.commands.from.metadata.upsert()` — Insert `METADATA` field, if it does - not exist. +- `.generic` + - `.listCommands()` — Lists all commands. Returns an iterator. + - `.findCommand()` — Finds a specific command by a predicate function. + - `.findCommandOption()` — Finds a specific command option by a predicate function. + - `.findCommandByName()` — Finds a specific command by name. + - `.findCommandOptionByName()` — Finds a specific command option by name. + - `.appendCommand()` — Add a new command to the AST. + - `.appendCommandOption()` — Add a new command option to a command. + - `.appendCommandArgument()` — Add a new main command argument to a command. + - `.removeCommand()` — Remove a command from the AST. + - `.removeCommandOption()` — Remove a command option from the AST. + - `.removeCommandArgument()` — Remove a command argument from the AST. +- `.commands` + - `.from` + - `.sources` + - `.list()` — List all `FROM` sources. + - `.find()` — Find a source by name. + - `.remove()` — Remove a source by name. + - `.insert()` — Insert a source. + - `.upsert()` — Insert a source, if it does not exist. + - `.metadata` + - `.list()` — List all `METADATA` fields. + - `.find()` — Find a `METADATA` field by name. + - `.removeByPredicate()` — Remove a `METADATA` field by matching a predicate function. + - `.remove()` — Remove a `METADATA` field by name. + - `.insert()` — Insert a `METADATA` field. + - `.upsert()` — Insert `METADATA` field, if it does not exist. + - `.limit` + - `.list()` — List all `LIMIT` commands. + - `.byIndex()` — Find a `LIMIT` command by index. + - `.find()` — Find a `LIMIT` command by a predicate function. + - `.remove()` — Remove a `LIMIT` command by index. + - `.set()` — Set the limit value of a specific `LIMIT` command. + - `.upsert()` — Insert a `LIMIT` command, or update the limit value if it already exists. diff --git a/packages/kbn-esql-ast/src/mutate/commands/from/index.ts b/packages/kbn-esql-ast/src/mutate/commands/from/index.ts index df76e072b346e..2a86a43dbe8d1 100644 --- a/packages/kbn-esql-ast/src/mutate/commands/from/index.ts +++ b/packages/kbn-esql-ast/src/mutate/commands/from/index.ts @@ -7,6 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import * as sources from './sources'; import * as metadata from './metadata'; -export { metadata }; +export { sources, metadata }; diff --git a/packages/kbn-esql-ast/src/mutate/commands/from/metadata.ts b/packages/kbn-esql-ast/src/mutate/commands/from/metadata.ts index 5892b028823aa..7f08fa2a5e946 100644 --- a/packages/kbn-esql-ast/src/mutate/commands/from/metadata.ts +++ b/packages/kbn-esql-ast/src/mutate/commands/from/metadata.ts @@ -157,7 +157,7 @@ export const insert = ( return; } - option = generic.insertCommandOption(command, 'metadata'); + option = generic.appendCommandOption(command, 'metadata'); } const parts: string[] = typeof fieldName === 'string' ? [fieldName] : fieldName; diff --git a/packages/kbn-esql-ast/src/mutate/commands/from/sources.test.ts b/packages/kbn-esql-ast/src/mutate/commands/from/sources.test.ts new file mode 100644 index 0000000000000..866a6dd8bdb20 --- /dev/null +++ b/packages/kbn-esql-ast/src/mutate/commands/from/sources.test.ts @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { parse } from '../../../parser'; +import { BasicPrettyPrinter } from '../../../pretty_print'; +import * as commands from '..'; + +describe('commands.from.sources', () => { + describe('.list()', () => { + it('returns empty array, if there are no sources', () => { + const src = 'ROW 123'; + const { root } = parse(src); + const list = [...commands.from.sources.list(root)]; + + expect(list.length).toBe(0); + }); + + it('returns a single source', () => { + const src = 'FROM index METADATA a'; + const { root } = parse(src); + const list = [...commands.from.sources.list(root)]; + + expect(list.length).toBe(1); + expect(list[0]).toMatchObject({ + type: 'source', + }); + }); + + it('returns all source fields', () => { + const src = 'FROM index, index2, cl:index3 METADATA a | LIMIT 88'; + const { root } = parse(src); + const list = [...commands.from.sources.list(root)]; + + expect(list).toMatchObject([ + { + type: 'source', + index: 'index', + }, + { + type: 'source', + index: 'index2', + }, + { + type: 'source', + index: 'index3', + cluster: 'cl', + }, + ]); + }); + }); + + describe('.find()', () => { + it('returns undefined if source is not found', () => { + const src = 'FROM index | WHERE a = b | LIMIT 123'; + const { root } = parse(src); + const source = commands.from.sources.find(root, 'abc'); + + expect(source).toBe(undefined); + }); + + it('can find a single source', () => { + const src = 'FROM index METADATA a'; + const { root } = parse(src); + const source = commands.from.sources.find(root, 'index')!; + + expect(source).toMatchObject({ + type: 'source', + name: 'index', + index: 'index', + }); + }); + + it('can find a source withing other sources', () => { + const src = 'FROM index, a, b, c:s1, s1, s2 METADATA a, b, c, _lang, _id'; + const { root } = parse(src); + const source1 = commands.from.sources.find(root, 's2')!; + const source2 = commands.from.sources.find(root, 's1', 'c')!; + + expect(source1).toMatchObject({ + type: 'source', + name: 's2', + index: 's2', + }); + expect(source2).toMatchObject({ + type: 'source', + name: 'c:s1', + index: 's1', + cluster: 'c', + }); + }); + }); + + describe('.remove()', () => { + it('can remove a source from a list', () => { + const src1 = 'FROM a, b, c'; + const { root } = parse(src1); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM a, b, c'); + + commands.from.sources.remove(root, 'b'); + + const src3 = BasicPrettyPrinter.print(root); + + expect(src3).toBe('FROM a, c'); + }); + + it('does nothing if source-to-delete does not exist', () => { + const src1 = 'FROM a, b, c'; + const { root } = parse(src1); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM a, b, c'); + + commands.from.sources.remove(root, 'd'); + + const src3 = BasicPrettyPrinter.print(root); + + expect(src3).toBe('FROM a, b, c'); + }); + }); + + describe('.insert()', () => { + it('can append a source', () => { + const src1 = 'FROM index METADATA a'; + const { root } = parse(src1); + + commands.from.sources.insert(root, 'index2'); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index, index2 METADATA a'); + }); + + it('can insert at specified position', () => { + const src1 = 'FROM a1, a2, a3'; + const { root } = parse(src1); + + commands.from.sources.insert(root, 'x', '', 0); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM x, a1, a2, a3'); + + commands.from.sources.insert(root, 'y', '', 2); + + const src3 = BasicPrettyPrinter.print(root); + + expect(src3).toBe('FROM x, a1, y, a2, a3'); + + commands.from.sources.insert(root, 'z', '', 4); + + const src4 = BasicPrettyPrinter.print(root); + + expect(src4).toBe('FROM x, a1, y, a2, z, a3'); + }); + + it('appends element, when insert position too high', () => { + const src1 = 'FROM a1, a2, a3'; + const { root } = parse(src1); + + commands.from.sources.insert(root, 'x', '', 999); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM a1, a2, a3, x'); + }); + + it('can inset the same source twice', () => { + const src1 = 'FROM index'; + const { root } = parse(src1); + + commands.from.sources.insert(root, 'x', '', 999); + commands.from.sources.insert(root, 'x', '', 999); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index, x, x'); + }); + }); + + describe('.upsert()', () => { + it('can append a source', () => { + const src1 = 'FROM index METADATA a'; + const { root } = parse(src1); + + commands.from.sources.upsert(root, 'index2'); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index, index2 METADATA a'); + }); + + it('can upsert at specified position', () => { + const src1 = 'FROM a1, a2, a3'; + const { root } = parse(src1); + + commands.from.sources.upsert(root, 'x', '', 0); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM x, a1, a2, a3'); + + commands.from.sources.upsert(root, 'y', '', 2); + + const src3 = BasicPrettyPrinter.print(root); + + expect(src3).toBe('FROM x, a1, y, a2, a3'); + + commands.from.sources.upsert(root, 'z', '', 4); + + const src4 = BasicPrettyPrinter.print(root); + + expect(src4).toBe('FROM x, a1, y, a2, z, a3'); + }); + + it('appends element, when upsert position too high', () => { + const src1 = 'FROM a1, a2, a3'; + const { root } = parse(src1); + + commands.from.sources.upsert(root, 'x', '', 999); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM a1, a2, a3, x'); + }); + + it('inserting already existing source is a no-op', () => { + const src1 = 'FROM index'; + const { root } = parse(src1); + + commands.from.sources.upsert(root, 'x', '', 999); + commands.from.sources.upsert(root, 'x', '', 999); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index, x'); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/mutate/commands/from/sources.ts b/packages/kbn-esql-ast/src/mutate/commands/from/sources.ts new file mode 100644 index 0000000000000..da67500b5b0bd --- /dev/null +++ b/packages/kbn-esql-ast/src/mutate/commands/from/sources.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Builder } from '../../../builder'; +import { ESQLAstQueryExpression, ESQLSource } from '../../../types'; +import { Visitor } from '../../../visitor'; +import * as generic from '../../generic'; +import * as util from '../../util'; +import type { Predicate } from '../../types'; + +export const list = (ast: ESQLAstQueryExpression): IterableIterator<ESQLSource> => { + return new Visitor() + .on('visitFromCommand', function* (ctx): IterableIterator<ESQLSource> { + for (const argument of ctx.arguments()) { + if (argument.type === 'source') { + yield argument; + } + } + }) + .on('visitCommand', function* (): IterableIterator<ESQLSource> {}) + .on('visitQuery', function* (ctx): IterableIterator<ESQLSource> { + for (const command of ctx.visitCommands()) { + yield* command; + } + }) + .visitQuery(ast); +}; + +export const findByPredicate = ( + ast: ESQLAstQueryExpression, + predicate: Predicate<ESQLSource> +): ESQLSource | undefined => { + return util.findByPredicate(list(ast), predicate); +}; + +export const find = ( + ast: ESQLAstQueryExpression, + index: string, + cluster?: string +): ESQLSource | undefined => { + return findByPredicate(ast, (source) => { + if (index !== source.index) { + return false; + } + if (typeof cluster === 'string' && cluster !== source.cluster) { + return false; + } + + return true; + }); +}; + +export const remove = ( + ast: ESQLAstQueryExpression, + index: string, + cluster?: string +): ESQLSource | undefined => { + const node = find(ast, index, cluster); + + if (!node) { + return undefined; + } + + const success = generic.removeCommandArgument(ast, node); + + return success ? node : undefined; +}; + +export const insert = ( + ast: ESQLAstQueryExpression, + indexName: string, + clusterName?: string, + index: number = -1 +): ESQLSource | undefined => { + const command = generic.findCommandByName(ast, 'from'); + + if (!command) { + return; + } + + const source = Builder.expression.indexSource(indexName, clusterName); + + if (index === -1) { + generic.appendCommandArgument(command, source); + } else { + command.args.splice(index, 0, source); + } + + return source; +}; + +export const upsert = ( + ast: ESQLAstQueryExpression, + indexName: string, + clusterName?: string, + index: number = -1 +): ESQLSource | undefined => { + const source = find(ast, indexName, clusterName); + + if (source) { + return source; + } + + return insert(ast, indexName, clusterName, index); +}; diff --git a/packages/kbn-esql-ast/src/mutate/commands/index.ts b/packages/kbn-esql-ast/src/mutate/commands/index.ts index cc3b7f446fa88..0a779292e6eca 100644 --- a/packages/kbn-esql-ast/src/mutate/commands/index.ts +++ b/packages/kbn-esql-ast/src/mutate/commands/index.ts @@ -8,5 +8,6 @@ */ import * as from from './from'; +import * as limit from './limit'; -export { from }; +export { from, limit }; diff --git a/packages/kbn-esql-ast/src/mutate/commands/limit/index.test.ts b/packages/kbn-esql-ast/src/mutate/commands/limit/index.test.ts new file mode 100644 index 0000000000000..9d734055cfeff --- /dev/null +++ b/packages/kbn-esql-ast/src/mutate/commands/limit/index.test.ts @@ -0,0 +1,311 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { parse } from '../../../parser'; +import { BasicPrettyPrinter } from '../../../pretty_print'; +import * as commands from '..'; + +describe('commands.limit', () => { + describe('.list()', () => { + it('lists all "LIMIT" commands', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const nodes = [...commands.limit.list(root)]; + + expect(nodes).toMatchObject([ + { + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + { + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 2, + }, + ], + }, + { + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 3, + }, + ], + }, + ]); + }); + }); + + describe('.byIndex()', () => { + it('retrieves the specific "LIMIT" command by index', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node = commands.limit.byIndex(root, 1); + + expect(node).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 2, + }, + ], + }); + }); + }); + + describe('.find()', () => { + it('can find a limit command by predicate', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node = commands.limit.find(root, (cmd) => (cmd.args?.[0] as any).value === 3); + + expect(node).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 3, + }, + ], + }); + }); + }); + + describe('.remove()', () => { + it('can remove the only limit command', () => { + const src = 'FROM index | WHERE a == b | LIMIT 123'; + const { root } = parse(src); + + const node = commands.limit.remove(root); + const src2 = BasicPrettyPrinter.print(root); + + expect(node).toMatchObject({ + type: 'command', + name: 'limit', + }); + expect(src2).toBe('FROM index | WHERE a == b'); + }); + + it('can remove the specific limit node', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node1 = commands.limit.remove(root, 1); + const src1 = BasicPrettyPrinter.print(root); + + expect(node1).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 2, + }, + ], + }); + expect(src1).toBe('FROM index | LIMIT 1 | STATS AGG() | WHERE a == b | LIMIT 3'); + + const node2 = commands.limit.remove(root); + const src2 = BasicPrettyPrinter.print(root); + + expect(node2).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }); + expect(src2).toBe('FROM index | STATS AGG() | WHERE a == b | LIMIT 3'); + + const node3 = commands.limit.remove(root); + const src3 = BasicPrettyPrinter.print(root); + + expect(node3).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 3, + }, + ], + }); + expect(src3).toBe('FROM index | STATS AGG() | WHERE a == b'); + + const node4 = commands.limit.remove(root); + + expect(node4).toBe(undefined); + }); + }); + + describe('.set()', () => { + it('can update a specific LIMIT command', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node1 = commands.limit.set(root, 2222, 1); + const node2 = commands.limit.set(root, 3333, 2); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe( + 'FROM index | LIMIT 1 | STATS AGG() | LIMIT 2222 | WHERE a == b | LIMIT 3333' + ); + expect(node1).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 2222, + }, + ], + }); + expect(node2).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 3333, + }, + ], + }); + }); + + it('by default, updates the first LIMIT command', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node = commands.limit.set(root, 99999999); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe( + 'FROM index | LIMIT 99999999 | STATS AGG() | LIMIT 2 | WHERE a == b | LIMIT 3' + ); + expect(node).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 99999999, + }, + ], + }); + }); + + it('does nothing if there is no existing limit command', () => { + const src = 'FROM index | STATS agg() | WHERE a == b'; + const { root } = parse(src); + + const node = commands.limit.set(root, 99999999); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index | STATS AGG() | WHERE a == b'); + expect(node).toBe(undefined); + }); + }); + + describe('.upsert()', () => { + it('can update a specific LIMIT command', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node1 = commands.limit.upsert(root, 2222, 1); + const node2 = commands.limit.upsert(root, 3333, 2); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe( + 'FROM index | LIMIT 1 | STATS AGG() | LIMIT 2222 | WHERE a == b | LIMIT 3333' + ); + expect(node1).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 2222, + }, + ], + }); + expect(node2).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 3333, + }, + ], + }); + }); + + it('by default, updates the first LIMIT command', () => { + const src = 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | WHERE a == b | LIMIT 3'; + const { root } = parse(src); + + const node = commands.limit.upsert(root, 99999999); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe( + 'FROM index | LIMIT 99999999 | STATS AGG() | LIMIT 2 | WHERE a == b | LIMIT 3' + ); + expect(node).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 99999999, + }, + ], + }); + }); + + it('inserts a new LIMIT command, if there is none existing', () => { + const src = 'FROM index | STATS agg() | WHERE a == b'; + const { root } = parse(src); + + const node = commands.limit.upsert(root, 99999999); + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index | STATS AGG() | WHERE a == b | LIMIT 99999999'); + expect(node).toMatchObject({ + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 99999999, + }, + ], + }); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/mutate/commands/limit/index.ts b/packages/kbn-esql-ast/src/mutate/commands/limit/index.ts new file mode 100644 index 0000000000000..937538e848328 --- /dev/null +++ b/packages/kbn-esql-ast/src/mutate/commands/limit/index.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Builder } from '../../../builder'; +import type { ESQLAstQueryExpression, ESQLCommand } from '../../../types'; +import * as generic from '../../generic'; +import { Predicate } from '../../types'; + +/** + * Lists all "LIMIT" commands in the query AST. + * + * @param ast The root AST node to search for "LIMIT" commands. + * @returns A collection of "LIMIT" commands. + */ +export const list = (ast: ESQLAstQueryExpression): IterableIterator<ESQLCommand> => { + return generic.listCommands(ast, (cmd) => cmd.name === 'limit'); +}; + +/** + * Retrieves the "LIMIT" command at the specified index in order of appearance. + * + * @param ast The root AST node to search for "LIMIT" commands. + * @param index The index of the "LIMIT" command to retrieve. + * @returns The "LIMIT" command at the specified index, if any. + */ +export const byIndex = (ast: ESQLAstQueryExpression, index: number): ESQLCommand | undefined => { + return [...list(ast)][index]; +}; + +/** + * Finds the first "LIMIT" command that satisfies the provided predicate. + * + * @param ast The root AST node to search for "LIMIT" commands. + * @param predicate The predicate function to apply to each "LIMIT" command. + * @returns The first "LIMIT" command that satisfies the predicate, if any. + */ +export const find = ( + ast: ESQLAstQueryExpression, + predicate: Predicate<ESQLCommand> +): ESQLCommand | undefined => { + return [...list(ast)].find(predicate); +}; + +/** + * Deletes the specified "LIMIT" command from the query AST. + * + * @param ast The root AST node to search for "LIMIT" commands. + * @param index The index of the "LIMIT" command to remove. + * @returns The removed "LIMIT" command, if any. + */ +export const remove = (ast: ESQLAstQueryExpression, index: number = 0): ESQLCommand | undefined => { + const command = generic.findCommandByName(ast, 'limit', index); + + if (!command) { + return; + } + + const success = generic.removeCommand(ast, command); + + if (!success) { + return; + } + + return command; +}; + +/** + * Sets the value of the specified "LIMIT" command. If `indexOrPredicate` is not + * specified will update the first "LIMIT" command found, if any. + * + * @param ast The root AST node to search for "LIMIT" commands. + * @param value The new value to set. + * @param indexOrPredicate The index of the "LIMIT" command to update, or a + * predicate function. + * @returns The updated "LIMIT" command, if any. + */ +export const set = ( + ast: ESQLAstQueryExpression, + value: number, + indexOrPredicate: number | Predicate<ESQLCommand> = 0 +): ESQLCommand | undefined => { + const node = + typeof indexOrPredicate === 'number' + ? byIndex(ast, indexOrPredicate) + : find(ast, indexOrPredicate); + + if (!node) { + return; + } + + const literal = Builder.expression.literal.numeric({ literalType: 'integer', value }); + + node.args = [literal]; + + return node; +}; + +/** + * Updates the value of the specified "LIMIT" command. If the "LIMIT" command + * is not found, a new one will be created and appended to the query AST. + * + * @param ast The root AST node to search for "LIMIT" commands. + * @param value The new value to set. + * @param indexOrPredicate The index of the "LIMIT" command to update, or a + * predicate function. + * @returns The updated or newly created "LIMIT" command. + */ +export const upsert = ( + ast: ESQLAstQueryExpression, + value: number, + indexOrPredicate: number | Predicate<ESQLCommand> = 0 +): ESQLCommand => { + const node = set(ast, value, indexOrPredicate); + + if (node) { + return node; + } + + const literal = Builder.expression.literal.numeric({ literalType: 'integer', value }); + const command = Builder.command({ + name: 'limit', + args: [literal], + }); + + generic.appendCommand(ast, command); + + return command; +}; diff --git a/packages/kbn-esql-ast/src/mutate/generic.test.ts b/packages/kbn-esql-ast/src/mutate/generic.test.ts index 14d951db1bccb..0109ff838ffda 100644 --- a/packages/kbn-esql-ast/src/mutate/generic.test.ts +++ b/packages/kbn-esql-ast/src/mutate/generic.test.ts @@ -97,6 +97,46 @@ describe('generic', () => { }); }); + describe('.removeCommand()', () => { + it('can remove the last command', () => { + const src = 'FROM index | LIMIT 10'; + const { root } = parse(src); + const command = generic.findCommandByName(root, 'limit', 0); + + generic.removeCommand(root, command!); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index'); + }); + + it('can remove the second command out of 3 with the same name', () => { + const src = 'FROM index | LIMIT 1 | LIMIT 2 | LIMIT 3'; + const { root } = parse(src); + const command = generic.findCommandByName(root, 'limit', 1); + + generic.removeCommand(root, command!); + + const src2 = BasicPrettyPrinter.print(root); + + expect(src2).toBe('FROM index | LIMIT 1 | LIMIT 3'); + }); + + it('can remove all commands', () => { + const src = 'FROM index | WHERE a == b | LIMIT 123'; + const { root } = parse(src); + const cmd1 = generic.findCommandByName(root, 'where'); + const cmd2 = generic.findCommandByName(root, 'limit'); + const cmd3 = generic.findCommandByName(root, 'from'); + + generic.removeCommand(root, cmd1!); + generic.removeCommand(root, cmd2!); + generic.removeCommand(root, cmd3!); + + expect(root.commands.length).toBe(0); + }); + }); + describe('.removeCommandOption()', () => { it('can remove existing command option', () => { const src = 'FROM index METADATA _score'; diff --git a/packages/kbn-esql-ast/src/mutate/generic.ts b/packages/kbn-esql-ast/src/mutate/generic.ts index 968eaf84f4a46..f27b0e2ae399f 100644 --- a/packages/kbn-esql-ast/src/mutate/generic.ts +++ b/packages/kbn-esql-ast/src/mutate/generic.ts @@ -7,8 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { isOptionNode } from '../ast/util'; import { Builder } from '../builder'; -import { ESQLAstQueryExpression, ESQLCommand, ESQLCommandOption } from '../types'; +import { + ESQLAstQueryExpression, + ESQLCommand, + ESQLCommandOption, + ESQLProperNode, + ESQLSingleAstItem, +} from '../types'; import { Visitor } from '../visitor'; import { Predicate } from './types'; @@ -124,6 +131,16 @@ export const findCommandOptionByName = ( return findCommandOption(command, (opt) => opt.name === optionName); }; +/** + * Adds a new command to the query AST node. + * + * @param ast The root AST node to append the command to. + * @param command The command AST node to append. + */ +export const appendCommand = (ast: ESQLAstQueryExpression, command: ESQLCommand): void => { + ast.commands.push(command); +}; + /** * Inserts a command option into the command's arguments list. The option can * be specified as a string or an AST node. @@ -132,7 +149,7 @@ export const findCommandOptionByName = ( * @param option The option to insert. * @returns The inserted option. */ -export const insertCommandOption = ( +export const appendCommandOption = ( command: ESQLCommand, option: string | ESQLCommandOption ): ESQLCommandOption => { @@ -145,6 +162,40 @@ export const insertCommandOption = ( return option; }; +export const appendCommandArgument = ( + command: ESQLCommand, + expression: ESQLSingleAstItem +): number => { + if (expression.type === 'option') { + command.args.push(expression); + return command.args.length - 1; + } + + const index = command.args.findIndex((arg) => isOptionNode(arg)); + + if (index > -1) { + command.args.splice(index, 0, expression); + return index; + } + + command.args.push(expression); + return command.args.length - 1; +}; + +export const removeCommand = (ast: ESQLAstQueryExpression, command: ESQLCommand): boolean => { + const cmds = ast.commands; + const length = cmds.length; + + for (let i = 0; i < length; i++) { + if (cmds[i] === command) { + cmds.splice(i, 1); + return true; + } + } + + return false; +}; + /** * Removes the first command option from the command's arguments list that * satisfies the predicate. @@ -196,3 +247,41 @@ export const removeCommandOption = ( }) .visitQuery(ast); }; + +/** + * Searches all command arguments in the query AST node and removes the node + * from the command's arguments list. + * + * @param ast The root AST node to search for command arguments. + * @param node The argument AST node to remove. + * @returns Returns true if the argument was removed, false otherwise. + */ +export const removeCommandArgument = ( + ast: ESQLAstQueryExpression, + node: ESQLProperNode +): boolean => { + return new Visitor() + .on('visitCommand', (ctx): boolean => { + const args = ctx.node.args; + const length = args.length; + + for (let i = 0; i < length; i++) { + if (args[i] === node) { + args.splice(i, 1); + return true; + } + } + + return false; + }) + .on('visitQuery', (ctx): boolean => { + for (const success of ctx.visitCommands()) { + if (success) { + return true; + } + } + + return false; + }) + .visitQuery(ast); +}; From c218e7cc29c00864d744d886c6e712b99ba97ed5 Mon Sep 17 00:00:00 2001 From: jennypavlova <dzheni.pavlova@elastic.co> Date: Tue, 15 Oct 2024 17:53:12 +0200 Subject: [PATCH 46/84] [APM][Otel] Fix an error with mobile services coming from synthtrace (#196313) Closes #196161 ## Summary This PR fixes an issue with the mobile data using synthtrace. After some investigation I saw that the the `httpSpan` was creating the spans with `transaction.type` set which resulted in `processor.event` being set to `transaction` instead of `span` - then with [the new required transaction fields](https://github.com/elastic/kibana/blob/adb558a86bafbe3567915c3fae252ff414147930/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_items.ts#L277) in get_trace_docs for transactions ([checking based on the processor.event](https://github.com/elastic/kibana/blob/adb558a86bafbe3567915c3fae252ff414147930/x-pack/plugins/observability_solution/apm/server/routes/traces/get_trace_items.ts#L352)) we were throwing an error because the transaction fields were not defined (which is expected because it's a span and not a transaction) ## Testing Generate mobile data using: `node scripts/synthtrace mobile.ts --clean` Open all the mobile traces (ios/Android) - there should not be an error --- packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts index 448c7e59a8ee8..b4dfafe22fc44 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts @@ -230,7 +230,7 @@ export class MobileDevice extends Entity<ApmFields> { spanSubtype: 'http', 'http.request.method': httpMethod, 'url.original': httpUrl, - 'transaction.type': 'mobile', + 'processor.event': 'span', }; if (this.networkConnection) { From 1f9bff8af16633b0f2921bea1522962ab40e737a Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:14:23 +0200 Subject: [PATCH 47/84] [Search] Handle insufficient privileges nicely on Serverless (#196160) ## Summary This adds a couple of callouts and disables unprivileged actions, so we don't bombard the user with ugly error messages when they click buttons or navigate to pages. It also: - Fixes a couple of TODO docLinks that were broken (oops) - Adds an errorhandler on all serverless search API routes so we surface issues to the user --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../configuration/connector_configuration.tsx | 3 + .../scheduling/connector_scheduling.tsx | 5 ++ .../components/scheduling/full_content.tsx | 8 ++- .../components/api_key/api_key.tsx | 66 ++++++++++++------- .../connector_scheduling.tsx | 33 ++++++---- .../connector_config/api_key_panel.tsx | 4 +- .../connector_config_fields.tsx | 7 +- .../connector_config_panels.tsx | 5 +- .../connector_configuration.tsx | 22 +++++-- .../connector_config/connector_index_name.tsx | 11 +++- .../connector_index_name_panel.tsx | 8 ++- .../connector_config/connector_overview.tsx | 14 ++-- .../connector_privileges_callout.tsx | 37 +++++++++++ .../connectors/connectors_table.tsx | 13 +++- .../components/connectors/edit_connector.tsx | 14 ++-- .../connectors/edit_description.tsx | 23 ++++--- .../components/connectors/edit_name.tsx | 4 +- .../connectors/edit_service_type.tsx | 5 +- .../connectors/empty_connectors_prompt.tsx | 8 ++- .../components/connectors_callout.tsx | 3 + .../components/connectors_ingestion.tsx | 31 +++++---- .../components/connectors_overview.tsx | 20 +++--- .../components/pipeline_manage_button.tsx | 5 +- .../components/pipeline_overview_button.tsx | 3 + .../application/hooks/api/use_api_key.tsx | 21 ++++++ .../application/hooks/api/use_connectors.tsx | 6 +- .../hooks/api/use_ingest_pipelines.tsx | 2 +- .../public/application/hooks/use_kibana.tsx | 2 + .../server/routes/api_key_routes.ts | 27 ++++++-- .../server/routes/connectors_routes.ts | 62 +++++++++-------- .../server/routes/indices_routes.ts | 19 +++--- .../server/routes/ingest_pipeline_routes.ts | 19 +++++- .../server/routes/mapping_routes.ts | 7 +- .../server/utils/error_handler.ts | 28 ++++++++ .../plugins/serverless_search/tsconfig.json | 3 + .../page_objects/svl_search_landing_page.ts | 4 +- .../test_suites/search/getting_started.ts | 2 +- 37 files changed, 400 insertions(+), 154 deletions(-) create mode 100644 x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx create mode 100644 x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx create mode 100644 x-pack/plugins/serverless_search/server/utils/error_handler.ts diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx index 8cb83176a6591..cd80b2489012e 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx @@ -43,6 +43,7 @@ function entryToDisplaylistItem(entry: ConfigEntryView): { description: string; interface ConnectorConfigurationProps { connector: Connector; hasPlatinumLicense: boolean; + isDisabled?: boolean; isLoading: boolean; saveConfig: (configuration: Record<string, string | number | boolean | null>) => void; saveAndSync?: (configuration: Record<string, string | number | boolean | null>) => void; @@ -89,6 +90,7 @@ export const ConnectorConfigurationComponent: FC< children, connector, hasPlatinumLicense, + isDisabled, isLoading, saveConfig, saveAndSync, @@ -207,6 +209,7 @@ export const ConnectorConfigurationComponent: FC< data-test-subj="entSearchContent-connector-configuration-editConfiguration" data-telemetry-id="entSearchContent-connector-overview-configuration-editConfiguration" onClick={() => setIsEditing(!isEditing)} + isDisabled={isDisabled} > {i18n.translate( 'searchConnectors.configurationConnector.config.editButton.title', diff --git a/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx b/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx index 3d8ea94b3599a..62521b3e2b3fa 100644 --- a/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx +++ b/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx @@ -66,6 +66,7 @@ interface ConnectorContentSchedulingProps { hasPlatinumLicense: boolean; hasChanges: boolean; hasIngestionError: boolean; + isDisabled?: boolean; setHasChanges: (changes: boolean) => void; shouldShowAccessControlSync: boolean; shouldShowIncrementalSync: boolean; @@ -81,6 +82,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr hasChanges, hasIngestionError, hasPlatinumLicense, + isDisabled, setHasChanges, shouldShowAccessControlSync, shouldShowIncrementalSync, @@ -140,6 +142,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr updateConnectorStatus={updateConnectorStatus} updateScheduling={updateScheduling} dataTelemetryIdPrefix={dataTelemetryIdPrefix} + isDisabled={isDisabled} /> </EuiFlexItem> {shouldShowIncrementalSync && ( @@ -153,6 +156,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr updateConnectorStatus={updateConnectorStatus} updateScheduling={updateScheduling} dataTelemetryIdPrefix={dataTelemetryIdPrefix} + isDisabled={isDisabled} /> </EuiFlexItem> )} @@ -186,6 +190,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr updateConnectorStatus={updateConnectorStatus} updateScheduling={updateScheduling} dataTelemetryIdPrefix={dataTelemetryIdPrefix} + isDisabled={isDisabled} /> </SchedulePanel> </EuiFlexItem> diff --git a/packages/kbn-search-connectors/components/scheduling/full_content.tsx b/packages/kbn-search-connectors/components/scheduling/full_content.tsx index de85f8fb2e4a9..3ec1fd4ab9e49 100644 --- a/packages/kbn-search-connectors/components/scheduling/full_content.tsx +++ b/packages/kbn-search-connectors/components/scheduling/full_content.tsx @@ -29,6 +29,7 @@ export interface ConnectorContentSchedulingProps { dataTelemetryIdPrefix: string; hasPlatinumLicense?: boolean; hasSyncTypeChanges: boolean; + isDisabled?: boolean; setHasChanges: (hasChanges: boolean) => void; setHasSyncTypeChanges: (state: boolean) => void; type: SyncJobType; @@ -104,6 +105,7 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp setHasSyncTypeChanges, hasPlatinumLicense = false, hasSyncTypeChanges, + isDisabled, type, updateConnectorStatus, updateScheduling, @@ -120,7 +122,9 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp !connector.configuration.use_document_level_security?.value; const isEnableSwitchDisabled = - type === SyncJobType.ACCESS_CONTROL && (!hasPlatinumLicense || isDocumentLevelSecurityDisabled); + (type === SyncJobType.ACCESS_CONTROL && + (!hasPlatinumLicense || isDocumentLevelSecurityDisabled)) || + Boolean(isDisabled); return ( <> @@ -217,7 +221,7 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp <ConnectorCronEditor hasSyncTypeChanges={hasSyncTypeChanges} setHasSyncTypeChanges={setHasSyncTypeChanges} - disabled={isGated} + disabled={isGated || Boolean(isDisabled)} scheduling={scheduling[type]} onReset={() => { setScheduling({ diff --git a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx index 296118410f868..38f880ec5298b 100644 --- a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx @@ -20,8 +20,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; -import { ApiKey } from '@kbn/security-plugin-types-common'; -import { useQuery } from '@tanstack/react-query'; import React, { useEffect, useState } from 'react'; import { ApiKeySelectableTokenField } from '@kbn/security-api-key-management'; import { @@ -32,6 +30,7 @@ import { useKibanaServices } from '../../hooks/use_kibana'; import { MANAGEMENT_API_KEYS } from '../../../../common/routes'; import { CreateApiKeyFlyout } from './create_api_key_flyout'; import './api_key.scss'; +import { useGetApiKeys } from '../../hooks/api/use_api_key'; function isCreatedResponse( value: SecurityCreateApiKeyResponse | SecurityUpdateApiKeyResponse @@ -45,15 +44,16 @@ function isCreatedResponse( export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: string) => void }) => { const { http, user } = useKibanaServices(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); - const { data } = useQuery({ - queryKey: ['apiKey'], - queryFn: () => http.fetch<{ apiKeys: ApiKey[] }>('/internal/serverless_search/api_keys'), - }); + const { data } = useGetApiKeys(); + const [apiKey, setApiKey] = useState<SecurityCreateApiKeyResponse | undefined>(undefined); const saveApiKey = (value: SecurityCreateApiKeyResponse) => { setApiKey(value); }; + // Prevent flickering in the most common case of having access to manage api keys + const canManageOwnApiKey = !data || data.canManageOwnApiKey; + useEffect(() => { if (apiKey) { setClientApiKey(apiKey.encoded); @@ -101,7 +101,7 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri </EuiStep> </EuiPanel> ) : ( - <EuiPanel> + <EuiPanel color={'plain'}> <EuiTitle size="xs"> <h3> {i18n.translate('xpack.serverlessSearch.apiKey.panel.title', { @@ -117,6 +117,16 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri })} </EuiText> <EuiSpacer size="l" /> + {!canManageOwnApiKey && ( + <> + <EuiBadge iconType="warningFilled"> + {i18n.translate('xpack.serverlessSearch.apiKey.panel.noUserPrivileges', { + defaultMessage: "You don't have access to manage API keys", + })} + </EuiBadge> + <EuiSpacer size="m" /> + </> + )} <EuiFlexGroup justifyContent="spaceBetween" alignItems="center"> <EuiFlexItem grow={false}> <EuiFlexGroup gutterSize="m"> @@ -127,6 +137,7 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri size="s" fill onClick={() => setIsFlyoutOpen(true)} + disabled={!canManageOwnApiKey} data-test-subj="new-api-key-button" aria-label={i18n.translate( 'xpack.serverlessSearch.apiKey.newButton.ariaLabel', @@ -143,24 +154,29 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri </EuiButton> </span> </EuiFlexItem> - <EuiFlexItem> - <span> - <EuiButton - iconType="popout" - size="s" - href={http.basePath.prepend(MANAGEMENT_API_KEYS)} - target="_blank" - data-test-subj="manage-api-keys-button" - aria-label={i18n.translate('xpack.serverlessSearch.apiKey.manage.ariaLabel', { - defaultMessage: 'Manage API keys', - })} - > - {i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', { - defaultMessage: 'Manage', - })} - </EuiButton> - </span> - </EuiFlexItem> + {canManageOwnApiKey && ( + <EuiFlexItem> + <span> + <EuiButton + iconType="popout" + size="s" + href={http.basePath.prepend(MANAGEMENT_API_KEYS)} + target="_blank" + data-test-subj="manage-api-keys-button" + aria-label={i18n.translate( + 'xpack.serverlessSearch.apiKey.manage.ariaLabel', + { + defaultMessage: 'Manage API keys', + } + )} + > + {i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', { + defaultMessage: 'Manage', + })} + </EuiButton> + </span> + </EuiFlexItem> + )} </EuiFlexGroup> </EuiFlexItem> <EuiFlexItem> diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx index 12961bfc4a093..f8c48cbb3c8ab 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx @@ -10,26 +10,33 @@ import { ConnectorSchedulingComponent } from '@kbn/search-connectors/components/ import { useConnectorScheduling } from '../../../hooks/api/use_update_connector_scheduling'; interface ConnectorSchedulingPanels { + canManageConnectors: boolean; connector: Connector; } -export const ConnectorScheduling: React.FC<ConnectorSchedulingPanels> = ({ connector }) => { +export const ConnectorScheduling: React.FC<ConnectorSchedulingPanels> = ({ + canManageConnectors, + connector, +}) => { const [hasChanges, setHasChanges] = useState<boolean>(false); const { isLoading, mutate } = useConnectorScheduling(connector.id); const hasIncrementalSyncFeature = connector?.features?.incremental_sync ?? false; const shouldShowIncrementalSync = hasIncrementalSyncFeature && (connector?.features?.incremental_sync?.enabled ?? false); return ( - <ConnectorSchedulingComponent - connector={connector} - dataTelemetryIdPrefix="serverlessSearch" - hasChanges={hasChanges} - hasIngestionError={connector?.status === ConnectorStatus.ERROR} - hasPlatinumLicense={false} - setHasChanges={setHasChanges} - shouldShowAccessControlSync={false} - shouldShowIncrementalSync={shouldShowIncrementalSync} - updateConnectorStatus={isLoading} - updateScheduling={mutate} - /> + <> + <ConnectorSchedulingComponent + connector={connector} + isDisabled={!canManageConnectors} + dataTelemetryIdPrefix="serverlessSearch" + hasChanges={hasChanges} + hasIngestionError={connector?.status === ConnectorStatus.ERROR} + hasPlatinumLicense={false} + setHasChanges={setHasChanges} + shouldShowAccessControlSync={false} + shouldShowIncrementalSync={shouldShowIncrementalSync} + updateConnectorStatus={isLoading} + updateScheduling={mutate} + /> + </> ); }; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx index eb1f4aba2ec2d..0ee9e3c638528 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx @@ -23,11 +23,13 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { OPTIONAL_LABEL } from '../../../../../common/i18n_string'; import { useCreateApiKey } from '../../../hooks/api/use_create_api_key'; +import { useGetApiKeys } from '../../../hooks/api/use_api_key'; interface ApiKeyPanelProps { connector: Connector; } export const ApiKeyPanel: React.FC<ApiKeyPanelProps> = ({ connector }) => { const { data, isLoading, mutate } = useCreateApiKey(); + const { data: apiKeysData } = useGetApiKeys(); return ( <EuiPanel hasBorder> <EuiFlexGroup direction="row" justifyContent="spaceBetween" alignItems="center"> @@ -59,7 +61,7 @@ export const ApiKeyPanel: React.FC<ApiKeyPanelProps> = ({ connector }) => { <span> <EuiButton data-test-subj="serverlessSearchApiKeyPanelNewApiKeyButton" - isDisabled={!connector.index_name} + isDisabled={!connector.index_name || !apiKeysData?.canManageOwnApiKey} isLoading={isLoading} iconType="plusInCircle" color="primary" diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_fields.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_fields.tsx index 021288ae12425..a3a6a347c5a83 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_fields.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_fields.tsx @@ -17,9 +17,13 @@ import { useEditConnectorConfiguration } from '../../../hooks/api/use_connector_ interface ConnectorConfigFieldsProps { connector: Connector; + isDisabled: boolean; } -export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({ connector }) => { +export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({ + connector, + isDisabled, +}) => { const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id); const { queryKey } = useConnector(connector.id); const queryClient = useQueryClient(); @@ -53,6 +57,7 @@ export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({ co <ConnectorConfigurationComponent connector={connector} hasPlatinumLicense={false} + isDisabled={isDisabled} isLoading={isLoading} saveConfig={mutate} /> diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx index e07874a4676e4..93e3881020b27 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx @@ -16,10 +16,12 @@ import { ConnectionDetails } from './connection_details_panel'; import { ConnectorIndexnamePanel } from './connector_index_name_panel'; interface ConnectorConfigurationPanels { + canManageConnectors: boolean; connector: Connector; } export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels> = ({ + canManageConnectors, connector, }) => { const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id); @@ -37,6 +39,7 @@ export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels <> <EuiPanel hasBorder> <ConnectorConfigurationComponent + isDisabled={!canManageConnectors} connector={connector} hasPlatinumLicense={false} isLoading={isLoading} @@ -46,7 +49,7 @@ export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels </EuiPanel> <EuiSpacer /> <EuiPanel hasBorder> - <ConnectorIndexnamePanel connector={connector} /> + <ConnectorIndexnamePanel canManageConnectors={canManageConnectors} connector={connector} /> </EuiPanel> <EuiSpacer /> <ConnectionDetails diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx index 9513ca197bb66..fb0332b812fda 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx @@ -28,6 +28,7 @@ import { ConnectorIndexName } from './connector_index_name'; import { ConnectorConfigurationPanels } from './connector_config_panels'; import { ConnectorOverview } from './connector_overview'; import { ConnectorScheduling } from '../conector_scheduling_tab/connector_scheduling'; +import { useConnectors } from '../../../hooks/api/use_connectors'; interface ConnectorConfigurationProps { connector: Connector; @@ -36,6 +37,8 @@ interface ConnectorConfigurationProps { type ConnectorConfigurationStep = 'link' | 'configure' | 'connect' | 'connected'; export const ConnectorConfiguration: React.FC<ConnectorConfigurationProps> = ({ connector }) => { + const { data } = useConnectors(); + const { canManageConnectors } = data || { canManageConnectors: false }; const [currentStep, setCurrentStep] = useState<ConnectorConfigurationStep>('link'); useEffect(() => { let step: ConnectorConfigurationStep = 'link'; @@ -99,7 +102,9 @@ export const ConnectorConfiguration: React.FC<ConnectorConfigurationProps> = ({ const tabs: EuiTabbedContentTab[] = [ { - content: <ConnectorOverview connector={connector} />, + content: ( + <ConnectorOverview canManageConnectors={canManageConnectors} connector={connector} /> + ), id: 'overview', name: OVERVIEW_LABEL, }, @@ -107,7 +112,10 @@ export const ConnectorConfiguration: React.FC<ConnectorConfigurationProps> = ({ content: ( <> <EuiSpacer /> - <ConnectorConfigurationPanels connector={connector} /> + <ConnectorConfigurationPanels + canManageConnectors={canManageConnectors} + connector={connector} + /> </> ), id: 'configuration', @@ -117,7 +125,7 @@ export const ConnectorConfiguration: React.FC<ConnectorConfigurationProps> = ({ content: ( <> <EuiSpacer /> - <ConnectorScheduling connector={connector} /> + <ConnectorScheduling canManageConnectors={canManageConnectors} connector={connector} /> </> ), id: 'scheduling', @@ -140,8 +148,12 @@ export const ConnectorConfiguration: React.FC<ConnectorConfigurationProps> = ({ status={connector.status} /> )} - {currentStep === 'configure' && <ConnectorConfigFields connector={connector} />} - {currentStep === 'connect' && <ConnectorIndexName connector={connector} />} + {currentStep === 'configure' && ( + <ConnectorConfigFields connector={connector} isDisabled={!canManageConnectors} /> + )} + {currentStep === 'connect' && ( + <ConnectorIndexName isDisabled={!canManageConnectors} connector={connector} /> + )} </EuiFlexItem> </EuiFlexGroup> ); diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx index a421af47a0a79..153c41c0332e9 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx @@ -32,9 +32,13 @@ import { docLinks } from '../../../../../common/doc_links'; import { DEFAULT_INGESTION_PIPELINE } from '../../../../../common'; interface ConnectorIndexNameProps { connector: Connector; + isDisabled?: boolean; } -export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connector }) => { +export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ + connector, + isDisabled, +}) => { const { http } = useKibanaServices(); const queryClient = useQueryClient(); const { queryKey } = useConnector(connector.id); @@ -88,6 +92,7 @@ export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connecto </EuiFlexGroup> <EuiSpacer /> <ConnectorIndexNameForm + isDisabled={isDisabled} indexName={newIndexName || ''} onChange={(name) => setNewIndexname(name)} /> @@ -145,7 +150,7 @@ export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connecto <EuiButton data-test-subj="serverlessSearchConnectorIndexNameButton" color="primary" - isDisabled={!isValidIndexName(newIndexName)} + isDisabled={!isValidIndexName(newIndexName) || isDisabled} isLoading={isLoading} onClick={() => mutate({ inputName: newIndexName, sync: false })} > @@ -162,7 +167,7 @@ export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connecto !( isValidIndexName(newIndexName) && [ConnectorStatus.CONFIGURED, ConnectorStatus.CONNECTED].includes(connector.status) - ) + ) || isDisabled } fill isLoading={isLoading} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx index ac472d9a4b440..3f4a51487a52e 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx @@ -17,9 +17,13 @@ import { ConnectorIndexNameForm } from './connector_index_name_form'; interface ConnectorIndexNamePanelProps { connector: Connector; + canManageConnectors: boolean; } -export const ConnectorIndexnamePanel: React.FC<ConnectorIndexNamePanelProps> = ({ connector }) => { +export const ConnectorIndexnamePanel: React.FC<ConnectorIndexNamePanelProps> = ({ + canManageConnectors, + connector, +}) => { const { http } = useKibanaServices(); const { data, isLoading, isSuccess, mutate, reset } = useMutation({ mutationFn: async (inputName: string) => { @@ -48,7 +52,7 @@ export const ConnectorIndexnamePanel: React.FC<ConnectorIndexNamePanelProps> = ( return ( <> <ConnectorIndexNameForm - isDisabled={isLoading} + isDisabled={isLoading || !canManageConnectors} indexName={newIndexName} onChange={(name) => setNewIndexName(name)} /> diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx index a4d79759d71cb..7a3d07eb5022b 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx @@ -21,10 +21,14 @@ import { useKibanaServices } from '../../../hooks/use_kibana'; import { SyncScheduledCallOut } from './sync_scheduled_callout'; interface ConnectorOverviewProps { + canManageConnectors: boolean; connector: Connector; } -export const ConnectorOverview: React.FC<ConnectorOverviewProps> = ({ connector }) => { +export const ConnectorOverview: React.FC<ConnectorOverviewProps> = ({ + canManageConnectors, + connector, +}) => { const { http } = useKibanaServices(); const queryClient = useQueryClient(); const { queryKey } = useConnector(connector.id); @@ -64,9 +68,11 @@ export const ConnectorOverview: React.FC<ConnectorOverviewProps> = ({ connector <EuiButton data-test-subj="serverlessSearchConnectorOverviewSyncButton" color="primary" - disabled={[ConnectorStatus.CREATED, ConnectorStatus.NEEDS_CONFIGURATION].includes( - connector.status - )} + disabled={ + [ConnectorStatus.CREATED, ConnectorStatus.NEEDS_CONFIGURATION].includes( + connector.status + ) || !canManageConnectors + } fill isLoading={isLoading} onClick={() => mutate()} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx new file mode 100644 index 0000000000000..d2b1d7196cffb --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx @@ -0,0 +1,37 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { useConnectors } from '../../../hooks/api/use_connectors'; + +export const ConnectorPrivilegesCallout: React.FC = () => { + const { data } = useConnectors(); + if (!data || (data.canManageConnectors && data.canReadConnectors)) { + return null; + } + const calloutTitle = i18n.translate('xpack.serverlessSearch.connectors.noPrivilegesTitle', { + defaultMessage: 'Insufficient access', + }); + return ( + <> + <EuiCallOut title={calloutTitle} color="warning" iconType="iInCircle"> + {data.canReadConnectors + ? i18n.translate('xpack.serverlessSearch.connectors.noManagePrivileges', { + defaultMessage: + 'You have read-only access to connectors. Contact your administrator for elevated privileges.', + }) + : i18n.translate('xpack.serverlessSearch.connectors.noPrivileges', { + defaultMessage: + "You don't have access to connectors. Contact your administrator for access.", + })} + </EuiCallOut> + <EuiSpacer /> + </> + ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx index 6f8dbd0edb4bc..4e559c74e8c87 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx @@ -211,7 +211,12 @@ export const ConnectorsTable: React.FC = () => { onClick: (connector: Connector) => copyToClipboard(connector.id), }, { - render: (connector: Connector) => <DeleteConnectorModalAction connector={connector} />, + render: (connector: Connector) => ( + <DeleteConnectorModalAction + connector={connector} + disabled={!data?.canManageConnectors} + /> + ), }, ], name: i18n.translate('xpack.serverlessSearch.connectors.actionsLabel', { @@ -287,7 +292,10 @@ export const ConnectorsTable: React.FC = () => { ); }; -const DeleteConnectorModalAction: React.FC<{ connector: Connector }> = ({ connector }) => { +const DeleteConnectorModalAction: React.FC<{ connector: Connector; disabled: boolean }> = ({ + connector, + disabled, +}) => { const [modalIsOpen, setModalIsOpen] = useState(false); return ( @@ -301,6 +309,7 @@ const DeleteConnectorModalAction: React.FC<{ connector: Connector }> = ({ connec )} <EuiToolTip content={DELETE_CONNECTOR_LABEL}> <EuiButtonIcon + disabled={disabled} data-test-subj="serverlessSearchDeleteConnectorModalActionButton" aria-label={DELETE_CONNECTOR_LABEL} onClick={() => setModalIsOpen(true)} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx index 2932ffc7bf79a..a8c9ef2cd52eb 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx @@ -33,10 +33,14 @@ import { EditDescription } from './edit_description'; import { DeleteConnectorModal } from './delete_connector_modal'; import { ConnectorConfiguration } from './connector_config/connector_configuration'; import { useConnector } from '../../hooks/api/use_connector'; +import { useConnectors } from '../../hooks/api/use_connectors'; +import { ConnectorPrivilegesCallout } from './connector_config/connector_privileges_callout'; export const EditConnector: React.FC = () => { const [deleteModalIsOpen, setDeleteModalIsOpen] = useState(false); const [menuIsOpen, setMenuIsOpen] = useState(false); + const { data: connectorsData } = useConnectors(); + const isDisabled = !connectorsData?.canManageConnectors; const { id } = useParams<{ id: string }>(); @@ -47,7 +51,7 @@ export const EditConnector: React.FC = () => { const { data, isLoading } = useConnector(id); - if (isLoading) { + if (!data || isLoading) { <EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchEditConnectorsPage"> <EuiPageTemplate.EmptyPrompt title={ @@ -97,7 +101,7 @@ export const EditConnector: React.FC = () => { <EuiText size="s">{CONNECTOR_LABEL}</EuiText> <EuiFlexGroup direction="row" justifyContent="spaceBetween"> <EuiFlexItem> - <EditName connector={connector} /> + <EditName connector={connector} isDisabled={isDisabled} /> </EuiFlexItem> <EuiFlexItem grow={false}> {deleteModalIsOpen && ( @@ -142,6 +146,7 @@ export const EditConnector: React.FC = () => { }, { name: DELETE_CONNECTOR_LABEL, + disabled: isDisabled, icon: 'trash', onClick: () => { setDeleteModalIsOpen(true); @@ -157,12 +162,13 @@ export const EditConnector: React.FC = () => { </EuiFlexGroup> </EuiPageTemplate.Section> <EuiPageTemplate.Section> + <ConnectorPrivilegesCallout /> <EuiFlexGroup direction="row"> <EuiFlexItem grow={1}> <EuiForm> - <EditServiceType connector={connector} /> + <EditServiceType isDisabled={isDisabled} connector={connector} /> <EuiSpacer /> - <EditDescription connector={connector} /> + <EditDescription isDisabled={isDisabled} connector={connector} /> </EuiForm> </EuiFlexItem> <EuiFlexItem grow={2}> diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx index 1749e1673e269..76f22f36f02af 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx @@ -25,10 +25,11 @@ import { useKibanaServices } from '../../hooks/use_kibana'; import { useConnector } from '../../hooks/api/use_connector'; interface EditDescriptionProps { + isDisabled?: boolean; connector: Connector; } -export const EditDescription: React.FC<EditDescriptionProps> = ({ connector }) => { +export const EditDescription: React.FC<EditDescriptionProps> = ({ connector, isDisabled }) => { const [isEditing, setIsEditing] = useState(false); const [newDescription, setNewDescription] = useState(connector.description || ''); const { http } = useKibanaServices(); @@ -66,15 +67,17 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({ connector }) = defaultMessage: 'Description', })} labelAppend={ - <EuiText size="xs"> - <EuiLink - data-test-subj="serverlessSearchEditDescriptionButton" - onClick={() => setIsEditing(true)} - role="button" - > - {EDIT_LABEL} - </EuiLink> - </EuiText> + isDisabled ? undefined : ( + <EuiText size="xs"> + <EuiLink + data-test-subj="serverlessSearchEditDescriptionButton" + onClick={() => setIsEditing(true)} + role="button" + > + {EDIT_LABEL} + </EuiLink> + </EuiText> + ) } fullWidth > diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx index b81fc51b07bcf..2fccd9554abaf 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx @@ -26,10 +26,11 @@ import { useKibanaServices } from '../../hooks/use_kibana'; import { useConnector } from '../../hooks/api/use_connector'; interface EditNameProps { + isDisabled?: boolean; connector: Connector; } -export const EditName: React.FC<EditNameProps> = ({ connector }) => { +export const EditName: React.FC<EditNameProps> = ({ connector, isDisabled }) => { const [isEditing, setIsEditing] = useState(false); const [newName, setNewName] = useState(connector.name || CONNECTOR_LABEL); const { http } = useKibanaServices(); @@ -77,6 +78,7 @@ export const EditName: React.FC<EditNameProps> = ({ connector }) => { > <EuiButtonIcon data-test-subj="serverlessSearchEditNameButton" + isDisabled={isDisabled} color="text" iconType="pencil" aria-label={i18n.translate('xpack.serverlessSearch.connectors.editNameLabel', { diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_service_type.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_service_type.tsx index a6598b4de15ea..2c428007034ec 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_service_type.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_service_type.tsx @@ -16,9 +16,10 @@ import { useConnector } from '../../hooks/api/use_connector'; interface EditServiceTypeProps { connector: Connector; + isDisabled?: boolean; } -export const EditServiceType: React.FC<EditServiceTypeProps> = ({ connector }) => { +export const EditServiceType: React.FC<EditServiceTypeProps> = ({ connector, isDisabled }) => { const { http } = useKibanaServices(); const connectorTypes = useConnectorTypes(); const queryClient = useQueryClient(); @@ -71,7 +72,7 @@ export const EditServiceType: React.FC<EditServiceTypeProps> = ({ connector }) = > <EuiSuperSelect // We only want to allow people to set the service type once to avoid weird conflicts - disabled={Boolean(connector.service_type)} + disabled={Boolean(connector.service_type) || isDisabled} data-test-subj="serverlessSearchEditConnectorTypeChoices" isLoading={isLoading} onChange={(event) => mutate(event)} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx index 5a90a9b4ff0da..56c7a9aaf8155 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx @@ -20,13 +20,16 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../common/doc_links'; import { useConnectorTypes } from '../../hooks/api/use_connector_types'; import { useCreateConnector } from '../../hooks/api/use_create_connector'; import { useAssetBasePath } from '../../hooks/use_asset_base_path'; +import { useConnectors } from '../../hooks/api/use_connectors'; export const EmptyConnectorsPrompt: React.FC = () => { const connectorTypes = useConnectorTypes(); const { createConnector, isLoading } = useCreateConnector(); + const { data } = useConnectors(); const assetBasePath = useAssetBasePath(); const connectorsPath = assetBasePath + '/connectors.svg'; @@ -94,7 +97,7 @@ export const EmptyConnectorsPrompt: React.FC = () => { source: ( <EuiLink data-test-subj="serverlessSearchEmptyConnectorsPromptSourceLink" - href="TODO TODO TODO" + href={docLinks.connectorsRunFromSource} > {i18n.translate( 'xpack.serverlessSearch.connectorsEmpty.sourceLabel', @@ -105,7 +108,7 @@ export const EmptyConnectorsPrompt: React.FC = () => { docker: ( <EuiLink data-test-subj="serverlessSearchEmptyConnectorsPromptDockerLink" - href="TODO TODO TODO" + href={docLinks.connectorsRunWithDocker} > {i18n.translate( 'xpack.serverlessSearch.connectorsEmpty.dockerLabel', @@ -167,6 +170,7 @@ export const EmptyConnectorsPrompt: React.FC = () => { <EuiFlexItem> <EuiButton data-test-subj="serverlessSearchEmptyConnectorsPromptCreateConnectorButton" + disabled={!data?.canManageConnectors} fill iconType="plusInCircleFilled" onClick={() => createConnector()} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx index 52fb878d4b619..625973a1f377a 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx @@ -10,9 +10,11 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useCreateConnector } from '../hooks/api/use_create_connector'; +import { useConnectors } from '../hooks/api/use_connectors'; export const ConnectorsCallout = () => { const { createConnector, isLoading } = useCreateConnector(); + const { data } = useConnectors(); return ( <EuiCallOut title={i18n.translate('xpack.serverlessSearch.selectClient.connectorsCallout.title', { @@ -31,6 +33,7 @@ export const ConnectorsCallout = () => { <EuiFlexItem grow={false}> <EuiButton color="primary" + disabled={!data?.canManageConnectors} iconType="plusInCircle" data-test-subj="connectors-callout-cta" onClick={() => createConnector()} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx index ede574abba12b..9f7f92b031e8f 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx @@ -19,9 +19,12 @@ import { GithubLink } from '@kbn/search-api-panels'; import React from 'react'; import { useCreateConnector } from '../hooks/api/use_create_connector'; +import { useConnectors } from '../hooks/api/use_connectors'; export const ConnectorIngestionPanel: React.FC<{ assetBasePath: string }> = ({ assetBasePath }) => { const { createConnector } = useCreateConnector(); + const { data } = useConnectors(); + return ( <EuiFlexGroup direction="column" justifyContent="spaceEvenly" gutterSize="s"> <EuiFlexItem grow={false}> @@ -49,19 +52,21 @@ export const ConnectorIngestionPanel: React.FC<{ assetBasePath: string }> = ({ a </EuiText> </EuiFlexItem> <EuiFlexGroup direction="row" justifyContent="flexStart" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiLink - data-test-subj="serverlessSearchConnectorIngestionPanelSetUpAConnectorLink" - onClick={() => createConnector()} - > - {i18n.translate( - 'xpack.serverlessSearch.ingestData.alternativeOptions.setupConnectorLabel', - { - defaultMessage: 'Set up a connector', - } - )} - </EuiLink> - </EuiFlexItem> + {Boolean(data?.canManageConnectors) && ( + <EuiFlexItem grow={false}> + <EuiLink + data-test-subj="serverlessSearchConnectorIngestionPanelSetUpAConnectorLink" + onClick={() => createConnector()} + > + {i18n.translate( + 'xpack.serverlessSearch.ingestData.alternativeOptions.setupConnectorLabel', + { + defaultMessage: 'Set up a connector', + } + )} + </EuiLink> + </EuiFlexItem> + )} <EuiFlexItem grow={false}> <GithubLink href="https://github.com/elastic/connectors" diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx index 26f7a85716a2d..f059a8d531eac 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx @@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo } from 'react'; +import { docLinks } from '../../../common/doc_links'; import { LEARN_MORE_LABEL } from '../../../common/i18n_string'; import { PLUGIN_ID } from '../../../common'; import { useConnectors } from '../hooks/api/use_connectors'; @@ -25,6 +26,7 @@ import { useCreateConnector } from '../hooks/api/use_create_connector'; import { useKibanaServices } from '../hooks/use_kibana'; import { EmptyConnectorsPrompt } from './connectors/empty_connectors_prompt'; import { ConnectorsTable } from './connectors/connectors_table'; +import { ConnectorPrivilegesCallout } from './connectors/connector_config/connector_privileges_callout'; export const ConnectorsOverview = () => { const { data, isLoading: connectorsLoading } = useConnectors(); @@ -35,6 +37,8 @@ export const ConnectorsOverview = () => { [consolePlugin] ); + const canManageConnectors = !data || data.canManageConnectors; + return ( <EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchConnectorsPage"> <EuiPageTemplate.Header @@ -76,6 +80,7 @@ export const ConnectorsOverview = () => { <EuiFlexItem> <EuiButton data-test-subj="serverlessSearchConnectorsOverviewCreateConnectorButton" + disabled={!canManageConnectors} isLoading={isLoading} fill iconType="plusInCircleFilled" @@ -100,7 +105,7 @@ export const ConnectorsOverview = () => { data-test-subj="serverlessSearchConnectorsOverviewLink" external target="_blank" - href={'TODO TODO'} + href={docLinks.connectors} > {LEARN_MORE_LABEL} </EuiLink> @@ -110,15 +115,14 @@ export const ConnectorsOverview = () => { </p> </EuiText> </EuiPageTemplate.Header> - {connectorsLoading || (data?.connectors || []).length > 0 ? ( - <EuiPageTemplate.Section restrictWidth color="subdued"> + <EuiPageTemplate.Section restrictWidth color="subdued"> + <ConnectorPrivilegesCallout /> + {connectorsLoading || (data?.connectors || []).length > 0 ? ( <ConnectorsTable /> - </EuiPageTemplate.Section> - ) : ( - <EuiPageTemplate.Section restrictWidth color="subdued"> + ) : ( <EmptyConnectorsPrompt /> - </EuiPageTemplate.Section> - )} + )} + </EuiPageTemplate.Section> {embeddableConsole} </EuiPageTemplate> ); diff --git a/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx b/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx index 15337cf6a7f30..14ec93402324b 100644 --- a/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx @@ -11,21 +11,24 @@ import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibanaServices } from '../hooks/use_kibana'; +import { useIngestPipelines } from '../hooks/api/use_ingest_pipelines'; export const PipelineManageButton: React.FC = () => { const { http } = useKibanaServices(); + const { data } = useIngestPipelines(); return ( <> <EuiSpacer /> <EuiButtonEmpty + disabled={!data?.canManagePipelines} size="m" href={http.basePath.prepend('/app/management/ingest/ingest_pipelines')} data-test-subj="manage-pipeline-button" > <EuiText size="s"> {i18n.translate('xpack.serverlessSearch.pipeline.description.manageButtonLabel', { - defaultMessage: 'Manage pipeline', + defaultMessage: 'Manage pipelines', })} </EuiText> </EuiButtonEmpty> diff --git a/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx b/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx index cc01bb538600a..d9be14209cb7e 100644 --- a/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx @@ -11,9 +11,11 @@ import { EuiSpacer, EuiText, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibanaServices } from '../hooks/use_kibana'; +import { useIngestPipelines } from '../hooks/api/use_ingest_pipelines'; export const PipelineOverviewButton: React.FC = () => { const { http } = useKibanaServices(); + const { data } = useIngestPipelines(); return ( <> @@ -21,6 +23,7 @@ export const PipelineOverviewButton: React.FC = () => { <EuiButton iconType="plusInCircle" size="m" + isDisabled={!data?.canManagePipelines} href={http.basePath.prepend('/app/management/ingest/ingest_pipelines/create')} data-test-subj="create-a-pipeline-button" > diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx new file mode 100644 index 0000000000000..cb0dce762ad14 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ApiKey } from '@kbn/security-plugin-types-common'; +import { useQuery } from '@tanstack/react-query'; +import { useKibanaServices } from '../use_kibana'; + +export const useGetApiKeys = () => { + const { http } = useKibanaServices(); + return useQuery({ + queryKey: ['apiKey'], + queryFn: () => + http.fetch<{ apiKeys: ApiKey[]; canManageOwnApiKey: boolean }>( + '/internal/serverless_search/api_keys' + ), + }); +}; diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx index f6de4223cfdce..2c880f3923149 100644 --- a/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx +++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx @@ -14,6 +14,10 @@ export const useConnectors = () => { return useQuery({ queryKey: ['fetchConnectors'], queryFn: () => - http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors'), + http.fetch<{ + connectors: Connector[]; + canManageConnectors: boolean; + canReadConnectors: boolean; + }>('/internal/serverless_search/connectors'), }); }; diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx index bd7d4292cfe18..5507ff16f7705 100644 --- a/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx +++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx @@ -14,7 +14,7 @@ export const useIngestPipelines = () => { return useQuery({ queryKey: ['fetchIngestPipelines'], queryFn: async () => - http.fetch<Record<string, IngestGetPipelineResponse>>( + http.fetch<{ pipelines: IngestGetPipelineResponse; canManagePipelines: boolean }>( `/internal/serverless_search/ingest_pipelines` ), }); diff --git a/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx b/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx index df73a27f9097d..2c659f9e3fe44 100644 --- a/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx +++ b/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx @@ -12,12 +12,14 @@ import type { SharePluginStart } from '@kbn/share-plugin/public'; import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public'; +import { SecurityPluginStart } from '@kbn/security-plugin-types-public'; export interface ServerlessSearchContext { cloud: CloudStart; console: ConsolePluginStart; history: AppMountParameters['history']; searchConnectors?: SearchConnectorsPluginStart; + security: SecurityPluginStart; share: SharePluginStart; user?: AuthenticatedUser; } diff --git a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts index 3d89d104eaa1c..a4f702a4d9d2b 100644 --- a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../plugin'; +import { errorHandler } from '../utils/error_handler'; export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDependencies) => { router.get( @@ -14,20 +15,32 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen path: '/internal/serverless_search/api_keys', validate: {}, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const core = await context.core; const { client } = core.elasticsearch; const user = core.security.authc.getCurrentUser(); if (user) { - const apiKeys = await client.asCurrentUser.security.getApiKey({ username: user.username }); - const validKeys = apiKeys.api_keys.filter(({ invalidated }) => !invalidated); - return response.ok({ body: { apiKeys: validKeys } }); + const privileges = await client.asCurrentUser.security.hasPrivileges({ + cluster: ['manage_own_api_key'], + }); + const canManageOwnApiKey = privileges?.cluster.manage_own_api_key; + + try { + const apiKeys = await client.asCurrentUser.security.getApiKey({ + username: user.username, + }); + + const validKeys = apiKeys.api_keys.filter(({ invalidated }) => !invalidated); + return response.ok({ body: { apiKeys: validKeys, canManageOwnApiKey } }); + } catch { + return response.ok({ body: { apiKeys: [], canManageOwnApiKey } }); + } } return response.customError({ statusCode: 502, body: 'Could not retrieve current user, security plugin is not ready', }); - } + }) ); router.post( @@ -37,7 +50,7 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen body: schema.any(), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const security = await getSecurity(); const result = await security.authc.apiKeys.create(request, request.body); @@ -49,6 +62,6 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen statusCode: 502, body: 'Could not retrieve current user, security plugin is not ready', }); - } + }) ); }; diff --git a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts index c57610dd9523a..09896a1808e4d 100644 --- a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts @@ -22,24 +22,32 @@ import { } from '@kbn/search-connectors'; import { DEFAULT_INGESTION_PIPELINE } from '../../common'; import { RouteDependencies } from '../plugin'; +import { errorHandler } from '../utils/error_handler'; -export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => { +export const registerConnectorsRoutes = ({ logger, http, router }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/connectors', validate: {}, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const connectors = await fetchConnectors(client.asCurrentUser); + const privileges = await client.asCurrentUser.security.hasPrivileges({ + index: [{ names: ['.elastic-connectors'], privileges: ['read', 'write'] }], + }); + const canManageConnectors = privileges.index['.elastic-connectors'].write; + const canReadConnectors = privileges.index['.elastic-connectors'].read; + + const connectors = canReadConnectors ? await fetchConnectors(client.asCurrentUser) : []; return response.ok({ body: { connectors, + canManageConnectors, + canReadConnectors, }, - headers: { 'content-type': 'application/json' }, }); - } + }) ); router.get( @@ -51,7 +59,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const connector = await fetchConnectorById(client.asCurrentUser, request.params.connectorId); @@ -63,7 +71,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => headers: { 'content-type': 'application/json' }, }) : response.notFound(); - } + }) ); router.post( @@ -71,7 +79,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => path: '/internal/serverless_search/connectors', validate: {}, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const defaultPipeline: IngestPipelineParams = { name: DEFAULT_INGESTION_PIPELINE, @@ -92,7 +100,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.post( @@ -107,7 +115,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await updateConnectorNameAndDescription( client.asCurrentUser, @@ -123,7 +131,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.post( @@ -138,7 +146,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await updateConnectorNameAndDescription( client.asCurrentUser, @@ -154,7 +162,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.post( @@ -169,7 +177,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; try { const result = await updateConnectorIndexName( @@ -186,7 +194,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => } catch (e) { return response.conflict({ body: e }); } - } + }) ); router.post( @@ -201,7 +209,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await updateConnectorServiceType( client.asCurrentUser, @@ -215,7 +223,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.delete( @@ -227,7 +235,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await deleteConnectorById(client.asCurrentUser, request.params.connectorId); return response.ok({ @@ -236,7 +244,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.post( @@ -254,7 +262,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await updateConnectorConfiguration( client.asCurrentUser, @@ -266,7 +274,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => body: result, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.post( @@ -278,7 +286,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await startConnectorSync(client.asCurrentUser, { connectorId: request.params.connectorId, @@ -288,7 +296,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => body: result, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.get( @@ -305,7 +313,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const result = await fetchSyncJobs( client.asCurrentUser, @@ -319,7 +327,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => body: result, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.post( { @@ -335,7 +343,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; await updateConnectorScheduling( client.asCurrentUser, @@ -343,6 +351,6 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => request.body ); return response.ok(); - } + }) ); }; diff --git a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts index 5c3e2187b1333..6b7ba424fde22 100644 --- a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts @@ -13,8 +13,9 @@ import { DEFAULT_DOCS_PER_PAGE } from '@kbn/search-index-documents/types'; import { fetchIndices } from '../lib/indices/fetch_indices'; import { fetchIndex } from '../lib/indices/fetch_index'; import { RouteDependencies } from '../plugin'; +import { errorHandler } from '../utils/error_handler'; -export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies) => { +export const registerIndicesRoutes = ({ logger, router }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/indices', @@ -26,7 +27,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const core = await context.core; const client = core.elasticsearch.client.asCurrentUser; const user = core.security.authc.getCurrentUser(); @@ -47,7 +48,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.get( @@ -59,7 +60,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const client = (await context.core).elasticsearch.client.asCurrentUser; const result = await client.indices.get({ @@ -74,7 +75,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }, headers: { 'content-type': 'application/json' }, }); - } + }) ); router.get( @@ -86,7 +87,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const body = await fetchIndex(client.asCurrentUser, request.params.indexName); return body @@ -95,7 +96,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies headers: { 'content-type': 'application/json' }, }) : response.notFound(); - } + }) ); router.post( @@ -119,7 +120,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const client = (await context.core).elasticsearch.client.asCurrentUser; const indexName = decodeURIComponent(request.params.index_name); const searchQuery = request.body.searchQuery; @@ -134,7 +135,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }, headers: { 'content-type': 'application/json' }, }); - } + }) ); }; diff --git a/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts b/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts index 0853540c66f04..349a637273135 100644 --- a/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts @@ -6,23 +6,36 @@ */ import { RouteDependencies } from '../plugin'; +import { errorHandler } from '../utils/error_handler'; -export const registerIngestPipelineRoutes = ({ router }: RouteDependencies) => { +export const registerIngestPipelineRoutes = ({ logger, router }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/ingest_pipelines', validate: {}, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; + const privileges = await client.asCurrentUser.security.hasPrivileges({ + cluster: ['manage_pipeline'], + }); + + const canManagePipelines = privileges?.cluster.manage_pipeline; + + if (!canManagePipelines) { + return response.ok({ + body: { pipelines: {}, canManagePipelines: false }, + }); + } const pipelines = await client.asCurrentUser.ingest.getPipeline(); return response.ok({ body: { pipelines, + canManagePipelines, }, headers: { 'content-type': 'application/json' }, }); - } + }) ); }; diff --git a/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts b/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts index bb6e22a1bd8fe..520de808b41c9 100644 --- a/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts @@ -7,8 +7,9 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../plugin'; +import { errorHandler } from '../utils/error_handler'; -export const registerMappingRoutes = ({ router }: RouteDependencies) => { +export const registerMappingRoutes = ({ logger, router }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/mappings/{index_name}', @@ -18,7 +19,7 @@ export const registerMappingRoutes = ({ router }: RouteDependencies) => { }), }, }, - async (context, request, response) => { + errorHandler(logger)(async (context, request, response) => { const { client } = (await context.core).elasticsearch; const mapping = await client.asCurrentUser.indices.getMapping({ expand_wildcards: ['open'], @@ -28,6 +29,6 @@ export const registerMappingRoutes = ({ router }: RouteDependencies) => { body: mapping[request.params.index_name], headers: { 'content-type': 'application/json' }, }); - } + }) ); }; diff --git a/x-pack/plugins/serverless_search/server/utils/error_handler.ts b/x-pack/plugins/serverless_search/server/utils/error_handler.ts new file mode 100644 index 0000000000000..b4b3894125bdb --- /dev/null +++ b/x-pack/plugins/serverless_search/server/utils/error_handler.ts @@ -0,0 +1,28 @@ +/* + * 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 { RequestHandlerWrapper } from '@kbn/core-http-server'; +import { KibanaServerError } from '@kbn/kibana-utils-plugin/common'; +import type { Logger } from '@kbn/logging'; + +function isKibanaServerError(error: any): error is KibanaServerError { + return error.statusCode && error.message; +} + +export const errorHandler: (logger: Logger) => RequestHandlerWrapper = (logger) => (handler) => { + return async (context, request, response) => { + try { + return await handler(context, request, response); + } catch (e) { + logger.error(e); + if (isKibanaServerError(e)) { + return response.customError({ statusCode: e.statusCode, body: e.message }); + } + throw e; + } + }; +}; diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index cc3b7b073dcee..0f7a803a68f7d 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -52,5 +52,8 @@ "@kbn/search-inference-endpoints", "@kbn/security-plugin-types-common", "@kbn/search-indices", + "@kbn/core-http-server", + "@kbn/logging", + "@kbn/security-plugin-types-public", ] } diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts index 3618fed58dcf3..4a9d858914dc6 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts @@ -75,7 +75,7 @@ export function SvlSearchLandingPageProvider({ getService }: FtrProviderContext) }, pipeline: { async createPipeline() { - await testSubjects.click('create-a-pipeline-button'); + await testSubjects.clickWhenNotDisabled('create-a-pipeline-button'); }, async expectNavigateToCreatePipelinePage() { expect(await browser.getCurrentUrl()).contain( @@ -83,7 +83,7 @@ export function SvlSearchLandingPageProvider({ getService }: FtrProviderContext) ); }, async managePipeline() { - await testSubjects.click('manage-pipeline-button'); + await testSubjects.clickWhenNotDisabled('manage-pipeline-button'); }, async expectNavigateToManagePipelinePage() { expect(await browser.getCurrentUrl()).contain('/app/management/ingest/ingest_pipelines'); diff --git a/x-pack/test_serverless/functional/test_suites/search/getting_started.ts b/x-pack/test_serverless/functional/test_suites/search/getting_started.ts index f521a03ccde85..dac9b2d7dae94 100644 --- a/x-pack/test_serverless/functional/test_suites/search/getting_started.ts +++ b/x-pack/test_serverless/functional/test_suites/search/getting_started.ts @@ -15,7 +15,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('getting started', function () { before(async () => { - await pageObjects.svlCommonPage.loginAsViewer(); + await pageObjects.svlCommonPage.loginAsAdmin(); }); it('has serverless side nav', async () => { From 30282056a058de78045e774910bea1cbb6c2e08f Mon Sep 17 00:00:00 2001 From: Philippe Oberti <philippe.oberti@elastic.co> Date: Tue, 15 Oct 2024 18:14:36 +0200 Subject: [PATCH 48/84] [SecuritySolution][Alert Details] - fix missing key console log error (#196201) --- .../public/flyout/document_details/right/header.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx index 189fe250fbab2..b7aea63ee9a24 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx @@ -64,6 +64,7 @@ export const PanelHeader: FC<PanelHeaderProps> = memo( isTourAnchor={isAlert} step={AlertsCasesTourSteps.reviewAlertDetailsFlyout} tourId={SecurityStepId.alertsCases} + key={index} > <EuiTab onClick={() => onSelectedTabChanged(tab.id)} From db2bd318a38d2d44034ddc310113e97af9ae2641 Mon Sep 17 00:00:00 2001 From: florent-leborgne <florent.leborgne@elastic.co> Date: Tue, 15 Oct 2024 18:15:00 +0200 Subject: [PATCH 49/84] [Docs] Add release notes for 8.15.3 (#196083) This PR adds release notes for Kibana 8.15.3. Rel: https://github.com/elastic/dev/issues/2833 Closes: https://github.com/elastic/platform-docs-team/issues/535 --------- Co-authored-by: David Kilfoyle <41695641+kilfoyle@users.noreply.github.com> --- docs/CHANGELOG.asciidoc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index b88939fdfdc84..44ce827d4d2fd 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <<release-notes-8.15.3>> * <<release-notes-8.15.2>> * <<release-notes-8.15.1>> * <<release-notes-8.15.0>> @@ -76,6 +77,44 @@ Review important information about the {kib} 8.x releases. include::upgrade-notes.asciidoc[] +[[release-notes-8.15.3]] +== {kib} 8.15.3 + +The 8.15.3 release includes the following bug fixes. + +[float] +[[fixes-v8.15.3]] +=== Bug fixes +Alerting:: +* Fixes a storage configuration error that could prevent the Stack Management > Alerts page from loading correctly ({kibana-pull}194785[#194785]). +* Fixes a bug preventing certain alerts with Role visibility set to "Stack Rules" from being shown on the Stack Management page ({kibana-pull}194615[#194615]). +* Fixes an issue where rules created from Discover before version 8.11.0 could no longer be accessed after upgrading ({kibana-pull}192321[#192321]). +Dashboards:: +* Fixes an issue where the `embed=true` parameter was missing when sharing a dashboard with the Embed code option ({kibana-pull}194366[#194366]). +Discover:: +* Fixes an issue with the document viewer panel not opening in focus mode ({kibana-pull}191039[#191039]). +Elastic Observability solution:: +* Fixes the OpenTelemetry guided onboarding for MacOS with x86_64 architectures ({kibana-pull}194915[#194915]). +* Fixes a bug where the SLO creation form was allowing multiple values for timestamp fields ({kibana-pull}194311[#194311]). +Elastic Search solution:: +* Fixes a bug with the https://www.elastic.co/guide/en/enterprise-search/8.15/connectors-network-drive.html[Network Drive connector] where advanced configuration fields were not displayed for CSV file role mappings with `Drive Type: Linux` selected ({kibana-pull}195567[#195567]). +Elastic Security solution:: +For the Elastic Security 8.15.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Kibana security:: +* Automatic Import no longer asks the LLM to map fields to reserved ECS fields ({kibana-pull}195168[#195168]). +* Automatic Import no longer returns an "Invalid ECS field" message when the ECS mapping slightly differs from the expected format. For example `date_format` instead of `date_formats` ({kibana-pull}195167[#195167]). +* Fixes an issue that was causing the Grok processor to return non-ECS compatible fields when processing structured or unstructured syslog samples in Automatic Import ({kibana-pull}194727[#194727]). +* Fixes the integrationName when uploading a new version of an existing integration using a ZIP upload ({kibana-pull}194298[#194298]). +* Fixes a bug that caused the Deploy step of Automatic Import to fail after a pipeline was edited and saved ({kibana-pull}194203[#194203]). +* Fixes an issue in the Kibana Management > Roles page where users could not sort the table by clicking the column headers ({kibana-pull}194196[#194196]). +Lens & Visualizations:: +* Fixes an issue where the legend label truncation setting wasn't working properly for heat maps in Lens ({kibana-pull}195928[#195928]). +Machine Learning:: +* Fixes an issue preventing Anomaly swim lane panels from updating on query changes ({kibana-pull}195090[#195090]). +* Fixes an issue that could cause the "rows per page" option to disappear from the Anomaly timeline view in the Anomaly Explorer ({kibana-pull}194531[#194531]). +* Fixes an issue causing screen flickering on the Results Explorer and Analytics Map pages when no jobs are available ({kibana-pull}193890[#193890]). + + [[release-notes-8.15.2]] == {kib} 8.15.2 From c119a6a8bca06cf107c80dc5068fe9c7c1754fa1 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet <nicolas.chaulet@elastic.co> Date: Tue, 15 Oct 2024 12:15:19 -0400 Subject: [PATCH 50/84] [Fleet] Fix input vars non correctly rendered in package policy editor (#195925) --- .../package_policy_input_config.test.tsx | 49 +++++++++++++++++++ .../package_policy_input_config.tsx | 6 ++- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx new file mode 100644 index 0000000000000..9295679e0aded --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { createFleetTestRendererMock } from '../../../../../../../../mock'; + +import { PackagePolicyInputConfig } from './package_policy_input_config'; + +describe('PackagePolicyInputConfig', () => { + function render(value = 'generic', datastreams: any = []) { + const renderer = createFleetTestRendererMock(); + const mockOnChange = jest.fn(); + + const utils = renderer.render( + <PackagePolicyInputConfig + hasInputStreams={false} + inputVarsValidationResults={{}} + packagePolicyInput={{ + enabled: true, + type: 'input', + streams: [], + }} + updatePackagePolicyInput={mockOnChange} + packageInputVars={[ + { + name: 'test', + title: 'Test', + type: 'text', + show_user: true, + }, + ]} + /> + ); + + return { utils, mockOnChange }; + } + + it('should support input vars with show_user:true without default value', () => { + const { utils } = render(); + + const inputEl = utils.findByTestId('textInput-test'); + expect(inputEl).toBeDefined(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx index 247f908668eab..e12a93e5bc9de 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx @@ -106,8 +106,10 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ <EuiFlexGroup direction="column" gutterSize="m"> {requiredVars.map((varDef) => { const { name: varName, type: varType } = varDef; - if (!packagePolicyInput.vars) return; - const { value, frozen } = packagePolicyInput.vars[varName]; + + const value = packagePolicyInput.vars?.[varName]?.value; + const frozen = packagePolicyInput.vars?.[varName]?.frozen; + return ( <EuiFlexItem key={varName}> <PackagePolicyInputVarField From fc3ce5475a73aad1abdbf857bc8787cd0f10aaed Mon Sep 17 00:00:00 2001 From: Ilya Nikokoshev <ilya.nikokoshev@elastic.co> Date: Tue, 15 Oct 2024 19:22:05 +0300 Subject: [PATCH 51/84] [Auto Import] Use larger number of samples on the backend (#196233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Release Notes Automatic Import now analyses larger number of samples to generate an integration. ## Summary Closes https://github.com/elastic/security-team/issues/9844 **Added: Backend Sampling** We pass 100 rows (these numeric values are adjustable) to the backend [^1] [^1]: As before, deterministically selected on the frontend, see https://github.com/elastic/kibana/pull/191598 The Categorization chain now processes the samples in batches, performing after initial categorization a number of review cycles (but not more than 5, tuned so that we stay under the 2 minute limit for a single API call). To decide when to stop processing we keep the list of _stable_ samples as follows: 1. The list is initially empty. 2. For each review we select a random subset of 40 samples, preferring to pick up the not-stable samples. 3. After each review – when the LLM potentially gives us new or changes the old processors – we compare the new pipeline results with the old pipeline results. 4. Those reviewed samples that did not change their categorization are added to the stable list. 5. Any samples that have changed their categorization are removed from the stable list. 6. If all samples are stable, we finish processing. **Removed: User Notification** Using 100 samples provides a balance between expected complexity and time budget we work with. We might want to change it in the future, possibly dynamically, making the specific number of no importance to the user. Thus we remove the truncation notification. **Unchanged:** - No batching is made in the related chain: it seems to work as-is. **Refactored:** - We centralize the sizing constants in the `x-pack/plugins/integration_assistant/common/constants.ts` file. - We remove the unused state key `formattedSamples` and combine `modelJSONInput` back into `modelInput`. > [!NOTE] > I had difficulty generating new graph diagrams, so they remain unchanged. --- .../__jest__/fixtures/categorization.ts | 9 +- .../__jest__/fixtures/related.ts | 1 - .../integration_assistant/common/constants.ts | 8 + .../integration_assistant/common/index.ts | 2 + .../utils.test.tsx => common/utils.test.ts} | 0 .../utils.tsx => common/utils.ts} | 0 .../sample_logs_input.test.tsx | 39 --- .../data_stream_step/sample_logs_input.tsx | 19 +- .../steps/data_stream_step/translations.ts | 5 - .../graphs/categorization/categorization.ts | 13 +- .../server/graphs/categorization/constants.ts | 1 + .../server/graphs/categorization/errors.ts | 1 - .../graphs/categorization/graph.test.ts | 31 +- .../server/graphs/categorization/graph.ts | 94 +++--- .../server/graphs/categorization/invalid.ts | 1 - .../server/graphs/categorization/review.ts | 13 +- .../server/graphs/categorization/stable.ts | 43 +++ .../server/graphs/categorization/util.test.ts | 270 ++++++++++++++++++ .../server/graphs/categorization/util.ts | 82 ++++++ .../server/graphs/categorization/validate.ts | 4 +- .../server/graphs/kv/validate.ts | 4 +- .../graphs/log_type_detection/detection.ts | 7 +- .../server/graphs/related/graph.ts | 46 +-- .../server/routes/categorization_routes.ts | 3 +- .../integration_assistant/server/types.ts | 8 +- .../server/util/graph.ts | 2 + .../server/util/pipeline.ts | 4 +- .../server/util/samples.ts | 11 - .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 31 files changed, 534 insertions(+), 190 deletions(-) rename x-pack/plugins/integration_assistant/{public/components/create_integration/create_integration_assistant/steps/data_stream_step/utils.test.tsx => common/utils.test.ts} (100%) rename x-pack/plugins/integration_assistant/{public/components/create_integration/create_integration_assistant/steps/data_stream_step/utils.tsx => common/utils.ts} (100%) create mode 100644 x-pack/plugins/integration_assistant/server/graphs/categorization/stable.ts create mode 100644 x-pack/plugins/integration_assistant/server/graphs/categorization/util.test.ts create mode 100644 x-pack/plugins/integration_assistant/server/graphs/categorization/util.ts diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts index 80366e7bd6f93..6867417bac0e2 100644 --- a/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts @@ -162,7 +162,6 @@ export const testPipelineInvalidEcs: { pipelineResults: object[]; errors: object export const categorizationTestState = { rawSamples: ['{"test1": "test1"}'], samples: ['{ "test1": "test1" }'], - formattedSamples: '{"test1": "test1"}', ecsTypes: 'testtypes', ecsCategories: 'testcategories', exAnswer: 'testanswer', @@ -173,9 +172,8 @@ export const categorizationTestState = { previousError: 'testprevious', previousInvalidCategorization: 'testinvalid', pipelineResults: [{ test: 'testresult' }], - finalized: false, - hasTriedOnce: false, - reviewed: false, + previousPipelineResults: [{ test: 'testresult' }], + lastReviewedSamples: [], currentPipeline: { test: 'testpipeline' }, currentProcessors: [ { @@ -193,6 +191,9 @@ export const categorizationTestState = { initialPipeline: categorizationInitialPipeline, results: { test: 'testresults' }, samplesFormat: { name: SamplesFormatName.Values.json }, + stableSamples: [], + reviewCount: 0, + finalized: false, }; export const categorizationMockProcessors = [ diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts index d96d845ae43b6..03ca8253768ff 100644 --- a/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts @@ -140,7 +140,6 @@ export const testPipelineValidResult: { pipelineResults: object[]; errors: objec export const relatedTestState = { rawSamples: ['{"test1": "test1"}'], samples: ['{ "test1": "test1" }'], - formattedSamples: '{"test1": "test1"}', ecs: 'testtypes', exAnswer: 'testanswer', packageName: 'testpackage', diff --git a/x-pack/plugins/integration_assistant/common/constants.ts b/x-pack/plugins/integration_assistant/common/constants.ts index d652f661f10bb..4d791341e34f9 100644 --- a/x-pack/plugins/integration_assistant/common/constants.ts +++ b/x-pack/plugins/integration_assistant/common/constants.ts @@ -36,3 +36,11 @@ export enum GenerationErrorCode { UNSUPPORTED_LOG_SAMPLES_FORMAT = 'unsupported-log-samples-format', UNPARSEABLE_CSV_DATA = 'unparseable-csv-data', } + +// Size limits +export const FRONTEND_SAMPLE_ROWS = 100; +export const LOG_FORMAT_DETECTION_SAMPLE_ROWS = 5; +export const CATEGORIZATION_INITIAL_BATCH_SIZE = 60; +export const CATEROGIZATION_REVIEW_BATCH_SIZE = 40; +export const CATEGORIZATION_REVIEW_MAX_CYCLES = 5; +export const CATEGORIZATION_RECURSION_LIMIT = 50; diff --git a/x-pack/plugins/integration_assistant/common/index.ts b/x-pack/plugins/integration_assistant/common/index.ts index b16254f9e11e2..0b13f7f692695 100644 --- a/x-pack/plugins/integration_assistant/common/index.ts +++ b/x-pack/plugins/integration_assistant/common/index.ts @@ -21,6 +21,8 @@ export { } from './api/analyze_logs/analyze_logs_route.gen'; export { CelInputRequestBody, CelInputResponse } from './api/cel/cel_input_route.gen'; +export { partialShuffleArray } from './utils'; + export type { DataStream, InputType, diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/utils.test.tsx b/x-pack/plugins/integration_assistant/common/utils.test.ts similarity index 100% rename from x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/utils.test.tsx rename to x-pack/plugins/integration_assistant/common/utils.test.ts diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/utils.tsx b/x-pack/plugins/integration_assistant/common/utils.ts similarity index 100% rename from x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/utils.tsx rename to x-pack/plugins/integration_assistant/common/utils.ts diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx index 6d8ad5eaf6d5c..8932ff5cfee5b 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx @@ -11,7 +11,6 @@ import { TestProvider } from '../../../../../mocks/test_provider'; import { parseNDJSON, parseJSONArray, SampleLogsInput } from './sample_logs_input'; import { ActionsProvider } from '../../state'; import { mockActions } from '../../mocks/state'; -import { mockServices } from '../../../../../services/mocks/services'; const wrapper: React.FC<React.PropsWithChildren<{}>> = ({ children }) => ( <TestProvider> @@ -165,25 +164,6 @@ describe('SampleLogsInput', () => { samplesFormat: { name: 'json', json_path: [] }, }); }); - - describe('when the file has too many rows', () => { - const tooLargeLogsSample = Array(6).fill(logsSampleRaw).join(','); // 12 entries - beforeEach(async () => { - await changeFile(input, new File([`[${tooLargeLogsSample}]`], 'test.json', { type })); - }); - - it('should truncate the logs sample', () => { - expect(mockActions.setIntegrationSettings).toBeCalledWith({ - logSamples: tooLargeLogsSample.split(',').slice(0, 2), - samplesFormat: { name: 'json', json_path: [] }, - }); - }); - it('should add a notification toast', () => { - expect(mockServices.notifications.toasts.addInfo).toBeCalledWith( - `The logs sample has been truncated to 10 rows.` - ); - }); - }); }); describe('when the file is a json array under a key', () => { @@ -236,25 +216,6 @@ describe('SampleLogsInput', () => { samplesFormat: { name: 'ndjson', multiline: false }, }); }); - - describe('when the file has too many rows', () => { - const tooLargeLogsSample = Array(6).fill(simpleNDJSON).join('\n'); // 12 entries - beforeEach(async () => { - await changeFile(input, new File([tooLargeLogsSample], 'test.json', { type })); - }); - - it('should truncate the logs sample', () => { - expect(mockActions.setIntegrationSettings).toBeCalledWith({ - logSamples: tooLargeLogsSample.split('\n').slice(0, 2), - samplesFormat: { name: 'ndjson', multiline: false }, - }); - }); - it('should add a notification toast', () => { - expect(mockServices.notifications.toasts.addInfo).toBeCalledWith( - `The logs sample has been truncated to 10 rows.` - ); - }); - }); }); describe('when the file is a an ndjson with a single record', () => { diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx index 5e33406ee5ea3..800be4cb89e5a 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx @@ -8,14 +8,12 @@ import React, { useCallback, useState } from 'react'; import { EuiCallOut, EuiFilePicker, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import { isPlainObject } from 'lodash/fp'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IntegrationSettings } from '../../types'; import * as i18n from './translations'; import { useActions } from '../../state'; import type { SamplesFormat } from '../../../../../../common'; -import { partialShuffleArray } from './utils'; - -const MaxLogsSampleRows = 10; +import { partialShuffleArray } from '../../../../../../common'; +import { FRONTEND_SAMPLE_ROWS } from '../../../../../../common/constants'; /** * Parse the logs sample file content as newiline-delimited JSON (NDJSON). @@ -83,8 +81,8 @@ export const parseJSONArray = ( * @returns Whether the array was truncated. */ function trimShuffleLogsSample<T>(array: T[]): boolean { - const willTruncate = array.length > MaxLogsSampleRows; - const numElements = willTruncate ? MaxLogsSampleRows : array.length; + const willTruncate = array.length > FRONTEND_SAMPLE_ROWS; + const numElements = willTruncate ? FRONTEND_SAMPLE_ROWS : array.length; partialShuffleArray(array, 1, numElements); @@ -215,7 +213,6 @@ interface SampleLogsInputProps { } export const SampleLogsInput = React.memo<SampleLogsInputProps>(({ integrationSettings }) => { - const { notifications } = useKibana().services; const { setIntegrationSettings } = useActions(); const [isParsing, setIsParsing] = useState(false); const [sampleFileError, setSampleFileError] = useState<string>(); @@ -266,11 +263,7 @@ export const SampleLogsInput = React.memo<SampleLogsInputProps>(({ integrationSe return; } - const { samplesFormat, logSamples, isTruncated } = prepareResult; - - if (isTruncated) { - notifications?.toasts.addInfo(i18n.LOGS_SAMPLE_TRUNCATED(MaxLogsSampleRows)); - } + const { samplesFormat, logSamples } = prepareResult; setIntegrationSettings({ ...integrationSettings, @@ -293,7 +286,7 @@ export const SampleLogsInput = React.memo<SampleLogsInputProps>(({ integrationSe reader.readAsText(logsSampleFile); }, - [integrationSettings, setIntegrationSettings, notifications?.toasts, setIsParsing] + [integrationSettings, setIntegrationSettings, setIsParsing] ); return ( <EuiFormRow diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts index 48793d20496d6..ec90568da0ef9 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts @@ -110,11 +110,6 @@ export const LOGS_SAMPLE_DESCRIPTION = i18n.translate( defaultMessage: 'Drag and drop a file or Browse files.', } ); -export const LOGS_SAMPLE_TRUNCATED = (maxRows: number) => - i18n.translate('xpack.integrationAssistant.step.dataStream.logsSample.truncatedWarning', { - values: { maxRows }, - defaultMessage: `The logs sample has been truncated to {maxRows} rows.`, - }); export const LOGS_SAMPLE_ERROR = { CAN_NOT_READ: i18n.translate( 'xpack.integrationAssistant.step.dataStream.logsSample.errorCanNotRead', diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.ts index 5dcc55d4f0975..515a7a6b6933a 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.ts @@ -11,6 +11,8 @@ import { combineProcessors } from '../../util/processors'; import { CATEGORIZATION_EXAMPLE_PROCESSORS } from './constants'; import { CATEGORIZATION_MAIN_PROMPT } from './prompts'; import type { CategorizationNodeParams } from './types'; +import { selectResults } from './util'; +import { CATEGORIZATION_INITIAL_BATCH_SIZE } from '../../../common/constants'; export async function handleCategorization({ state, @@ -19,8 +21,15 @@ export async function handleCategorization({ const categorizationMainPrompt = CATEGORIZATION_MAIN_PROMPT; const outputParser = new JsonOutputParser(); const categorizationMainGraph = categorizationMainPrompt.pipe(model).pipe(outputParser); + + const [pipelineResults, _] = selectResults( + state.pipelineResults, + CATEGORIZATION_INITIAL_BATCH_SIZE, + new Set(state.stableSamples) + ); + const currentProcessors = (await categorizationMainGraph.invoke({ - pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + pipeline_results: JSON.stringify(pipelineResults, null, 2), example_processors: CATEGORIZATION_EXAMPLE_PROCESSORS, ex_answer: state?.exAnswer, ecs_categories: state?.ecsCategories, @@ -36,7 +45,7 @@ export async function handleCategorization({ return { currentPipeline, currentProcessors, - hasTriedOnce: true, + lastReviewedSamples: [], lastExecutedChain: 'categorization', }; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts index 11b510bb09a93..c425dcee24eab 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + export const ECS_CATEGORIES = { api: 'Covers events from API calls, including those from OS and network protocols. Allowed event.type combinations: access, admin, allowed, change, creation, deletion, denied, end, info, start, user', authentication: diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts index 789673af0ff28..ee6a26d436f96 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts @@ -39,7 +39,6 @@ export async function handleErrors({ return { currentPipeline, currentProcessors, - reviewed: false, lastExecutedChain: 'error', }; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts index 8db8a8019a1ed..bf2d6dba6165e 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts @@ -25,6 +25,7 @@ import { handleReview } from './review'; import { handleCategorization } from './categorization'; import { handleErrors } from './errors'; import { handleInvalidCategorization } from './invalid'; +import { handleUpdateStableSamples } from './stable'; import { testPipeline, combineProcessors } from '../../util'; import { ActionsClientChatOpenAI, @@ -39,6 +40,7 @@ jest.mock('./errors'); jest.mock('./review'); jest.mock('./categorization'); jest.mock('./invalid'); +jest.mock('./stable'); jest.mock('../../util/pipeline', () => ({ testPipeline: jest.fn(), @@ -74,7 +76,8 @@ describe('runCategorizationGraph', () => { return { currentPipeline, currentProcessors, - reviewed: false, + stableSamples: [], + reviewCount: 0, finalized: false, lastExecutedChain: 'categorization', }; @@ -90,7 +93,8 @@ describe('runCategorizationGraph', () => { return { currentPipeline, currentProcessors, - reviewed: false, + stableSamples: [], + reviewCount: 0, finalized: false, lastExecutedChain: 'error', }; @@ -106,7 +110,8 @@ describe('runCategorizationGraph', () => { return { currentPipeline, currentProcessors, - reviewed: false, + stableSamples: [], + reviewCount: 0, finalized: false, lastExecutedChain: 'invalidCategorization', }; @@ -122,11 +127,29 @@ describe('runCategorizationGraph', () => { return { currentProcessors, currentPipeline, - reviewed: true, + stableSamples: [], + reviewCount: 0, finalized: false, lastExecutedChain: 'review', }; }); + // After the review it should route to modelOutput and finish. + (handleUpdateStableSamples as jest.Mock) + .mockResolvedValueOnce({ + stableSamples: [], + finalized: false, + lastExecutedChain: 'handleUpdateStableSamples', + }) + .mockResolvedValueOnce({ + stableSamples: [], + finalized: false, + lastExecutedChain: 'handleUpdateStableSamples', + }) + .mockResolvedValueOnce({ + stableSamples: [0], + finalized: false, + lastExecutedChain: 'handleUpdateStableSamples', + }); }); it('Ensures that the graph compiles', async () => { diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts index 227bcd6939b94..2f07bcd106862 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts @@ -10,7 +10,7 @@ import { StateGraph, END, START } from '@langchain/langgraph'; import { SamplesFormat } from '../../../common'; import type { CategorizationState } from '../../types'; import { handleValidatePipeline } from '../../util/graph'; -import { formatSamples, prefixSamples } from '../../util/samples'; +import { prefixSamples } from '../../util/samples'; import { handleCategorization } from './categorization'; import { CATEGORIZATION_EXAMPLE_ANSWER, ECS_CATEGORIES, ECS_TYPES } from './constants'; import { handleErrors } from './errors'; @@ -18,6 +18,8 @@ import { handleInvalidCategorization } from './invalid'; import { handleReview } from './review'; import type { CategorizationBaseNodeParams, CategorizationGraphParams } from './types'; import { handleCategorizationValidation } from './validate'; +import { handleUpdateStableSamples } from './stable'; +import { CATEGORIZATION_REVIEW_MAX_CYCLES } from '../../../common/constants'; const graphState: StateGraphArgs<CategorizationState>['channels'] = { lastExecutedChain: { @@ -32,10 +34,6 @@ const graphState: StateGraphArgs<CategorizationState>['channels'] = { value: (x: string[], y?: string[]) => y ?? x, default: () => [], }, - formattedSamples: { - value: (x: string, y?: string) => y ?? x, - default: () => '', - }, ecsTypes: { value: (x: string, y?: string) => y ?? x, default: () => '', @@ -60,13 +58,13 @@ const graphState: StateGraphArgs<CategorizationState>['channels'] = { value: (x: boolean, y?: boolean) => y ?? x, default: () => false, }, - reviewed: { - value: (x: boolean, y?: boolean) => y ?? x, - default: () => false, + stableSamples: { + value: (x: number[], y: number[]) => y ?? x, + default: () => [], }, - hasTriedOnce: { - value: (x: boolean, y?: boolean) => y ?? x, - default: () => false, + reviewCount: { + value: (x: number, y: number) => y ?? x, + default: () => 0, }, errors: { value: (x: object, y?: object) => y ?? x, @@ -80,6 +78,14 @@ const graphState: StateGraphArgs<CategorizationState>['channels'] = { value: (x: object[], y?: object[]) => y ?? x, default: () => [{}], }, + previousPipelineResults: { + value: (x: object[], y?: object[]) => y ?? x, + default: () => [{}], + }, + lastReviewedSamples: { + value: (x: number[], y: number[]) => y ?? x, + default: () => [], + }, currentPipeline: { value: (x: object, y?: object) => y ?? x, default: () => ({}), @@ -110,33 +116,22 @@ const graphState: StateGraphArgs<CategorizationState>['channels'] = { }, }; -function modelJSONInput({ state }: CategorizationBaseNodeParams): Partial<CategorizationState> { - const samples = prefixSamples(state); - const formattedSamples = formatSamples(samples); - const initialPipeline = JSON.parse(JSON.stringify(state.currentPipeline)); - return { - exAnswer: JSON.stringify(CATEGORIZATION_EXAMPLE_ANSWER, null, 2), - ecsCategories: JSON.stringify(ECS_CATEGORIES, null, 2), - ecsTypes: JSON.stringify(ECS_TYPES, null, 2), - samples, - formattedSamples, - initialPipeline, - finalized: false, - reviewed: false, - lastExecutedChain: 'modelJSONInput', - }; -} - function modelInput({ state }: CategorizationBaseNodeParams): Partial<CategorizationState> { + let samples: string[]; + if (state.samplesFormat.name === 'json' || state.samplesFormat.name === 'ndjson') { + samples = prefixSamples(state); + } else { + samples = state.rawSamples; + } + const initialPipeline = JSON.parse(JSON.stringify(state.currentPipeline)); return { exAnswer: JSON.stringify(CATEGORIZATION_EXAMPLE_ANSWER, null, 2), ecsCategories: JSON.stringify(ECS_CATEGORIES, null, 2), ecsTypes: JSON.stringify(ECS_TYPES, null, 2), - samples: state.rawSamples, + samples, initialPipeline, - finalized: false, - reviewed: false, + stableSamples: [], lastExecutedChain: 'modelInput', }; } @@ -152,16 +147,9 @@ function modelOutput({ state }: CategorizationBaseNodeParams): Partial<Categoriz }; } -function modelRouter({ state }: CategorizationBaseNodeParams): string { - if (state.samplesFormat.name === 'json' || state.samplesFormat.name === 'ndjson') { - return 'modelJSONInput'; - } - return 'modelInput'; -} - function validationRouter({ state }: CategorizationBaseNodeParams): string { if (Object.keys(state.currentProcessors).length === 0) { - if (state.hasTriedOnce || state.reviewed) { + if (state.stableSamples.length === state.pipelineResults.length) { return 'modelOutput'; } return 'categorization'; @@ -171,24 +159,27 @@ function validationRouter({ state }: CategorizationBaseNodeParams): string { function chainRouter({ state }: CategorizationBaseNodeParams): string { if (Object.keys(state.currentProcessors).length === 0) { - if (state.hasTriedOnce || state.reviewed) { + if (state.stableSamples.length === state.pipelineResults.length) { return 'modelOutput'; } } + if (Object.keys(state.errors).length > 0) { return 'errors'; } + if (Object.keys(state.invalidCategorization).length > 0) { return 'invalidCategorization'; } - if (!state.reviewed) { + + if ( + state.stableSamples.length < state.pipelineResults.length && + state.reviewCount < CATEGORIZATION_REVIEW_MAX_CYCLES + ) { return 'review'; } - if (!state.finalized) { - return 'modelOutput'; - } - return END; + return 'modelOutput'; } export async function getCategorizationGraph({ client, model }: CategorizationGraphParams) { @@ -196,7 +187,6 @@ export async function getCategorizationGraph({ client, model }: CategorizationGr channels: graphState, }) .addNode('modelInput', (state: CategorizationState) => modelInput({ state })) - .addNode('modelJSONInput', (state: CategorizationState) => modelJSONInput({ state })) .addNode('modelOutput', (state: CategorizationState) => modelOutput({ state })) .addNode('handleCategorization', (state: CategorizationState) => handleCategorization({ state, model }) @@ -204,6 +194,9 @@ export async function getCategorizationGraph({ client, model }: CategorizationGr .addNode('handleValidatePipeline', (state: CategorizationState) => handleValidatePipeline({ state, client }) ) + .addNode('handleUpdateStableSamples', (state: CategorizationState) => + handleUpdateStableSamples({ state }) + ) .addNode('handleCategorizationValidation', (state: CategorizationState) => handleCategorizationValidation({ state }) ) @@ -212,19 +205,16 @@ export async function getCategorizationGraph({ client, model }: CategorizationGr ) .addNode('handleErrors', (state: CategorizationState) => handleErrors({ state, model })) .addNode('handleReview', (state: CategorizationState) => handleReview({ state, model })) - .addConditionalEdges(START, (state: CategorizationState) => modelRouter({ state }), { - modelJSONInput: 'modelJSONInput', - modelInput: 'modelInput', // For Non JSON input samples - }) + .addEdge(START, 'modelInput') .addEdge('modelOutput', END) - .addEdge('modelJSONInput', 'handleValidatePipeline') .addEdge('modelInput', 'handleValidatePipeline') .addEdge('handleCategorization', 'handleValidatePipeline') .addEdge('handleInvalidCategorization', 'handleValidatePipeline') .addEdge('handleErrors', 'handleValidatePipeline') .addEdge('handleReview', 'handleValidatePipeline') + .addEdge('handleValidatePipeline', 'handleUpdateStableSamples') .addConditionalEdges( - 'handleValidatePipeline', + 'handleUpdateStableSamples', (state: CategorizationState) => validationRouter({ state }), { modelOutput: 'modelOutput', diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts index 62f7f3101ba9a..18c3f87a55814 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts @@ -39,7 +39,6 @@ export async function handleInvalidCategorization({ return { currentPipeline, currentProcessors, - reviewed: false, lastExecutedChain: 'invalidCategorization', }; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts index 19b8180ce33e5..9a842b2b83107 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts @@ -12,6 +12,8 @@ import type { CategorizationNodeParams } from './types'; import type { SimplifiedProcessors, SimplifiedProcessor, CategorizationState } from '../../types'; import { combineProcessors } from '../../util/processors'; import { ECS_EVENT_TYPES_PER_CATEGORY } from './constants'; +import { selectResults } from './util'; +import { CATEROGIZATION_REVIEW_BATCH_SIZE } from '../../../common/constants'; export async function handleReview({ state, @@ -21,9 +23,15 @@ export async function handleReview({ const outputParser = new JsonOutputParser(); const categorizationReview = categorizationReviewPrompt.pipe(model).pipe(outputParser); + const [pipelineResults, selectedIndices] = selectResults( + state.pipelineResults, + CATEROGIZATION_REVIEW_BATCH_SIZE, + new Set(state.stableSamples) + ); + const currentProcessors = (await categorizationReview.invoke({ current_processors: JSON.stringify(state.currentProcessors, null, 2), - pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + pipeline_results: JSON.stringify(pipelineResults, null, 2), previous_invalid_categorization: state.previousInvalidCategorization, previous_error: state.previousError, ex_answer: state?.exAnswer, @@ -41,7 +49,8 @@ export async function handleReview({ return { currentPipeline, currentProcessors, - reviewed: true, + reviewCount: state.reviewCount + 1, + lastReviewedSamples: selectedIndices, lastExecutedChain: 'review', }; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/stable.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/stable.ts new file mode 100644 index 0000000000000..c552dfd950028 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/stable.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CategorizationState } from '../../types'; +import type { CategorizationBaseNodeParams } from './types'; +import { diffCategorization } from './util'; + +/** + * Updates the stable samples in the categorization state. + * + * Example: If the pipeline results are [A, B, C, D], the previous pipeline results are [A, X, C, D], + * the previously stable samples are {0, 1} and the last reviewed samples are {1, 2}, then 1 will be removed from + * the list of stable samples and 2 will be added to the list of stable samples. The new set will be {0, 2}. + * + * @param {CategorizationBaseNodeParams} params - The parameters containing the current state. + * @returns {Partial<CategorizationState>} - The updated categorization state with new stable samples, + * cleared last reviewed samples, and the last executed chain set to 'handleUpdateStableSamples'. + */ +export function handleUpdateStableSamples({ + state, +}: CategorizationBaseNodeParams): Partial<CategorizationState> { + if (state.previousPipelineResults.length === 0) { + return {}; + } + + const diff = diffCategorization(state.pipelineResults, state.previousPipelineResults); + + const newStableSamples = Array.from( + new Set<number>( + [...state.stableSamples, ...state.lastReviewedSamples].filter((x) => !diff.has(x)) + ) + ); + + return { + stableSamples: newStableSamples, + lastReviewedSamples: [], + lastExecutedChain: 'handleUpdateStableSamples', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/util.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/util.test.ts new file mode 100644 index 0000000000000..72f4a7f4eeeaf --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/util.test.ts @@ -0,0 +1,270 @@ +/* + * 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 { selectResults, diffCategorization, stringArraysEqual } from './util'; +import { partialShuffleArray } from '../../../common'; +import type { PipelineResult } from './validate'; + +// Mock the partialShuffleArray function +jest.mock('../../../common', () => ({ + partialShuffleArray: jest.fn(), +})); + +describe('selectResults', () => { + const mockPartialShuffleArray = partialShuffleArray as jest.MockedFunction< + typeof partialShuffleArray + >; + + beforeEach(() => { + mockPartialShuffleArray.mockClear(); + }); + + it('should return the correct number of samples and their indices', () => { + const pipelineResults = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + { event: { category: ['3'] } }, + ] satisfies PipelineResult[]; + const maxSamples = 2; + + mockPartialShuffleArray.mockImplementation((array, numSamples) => { + // Mock implementation that does not actually shuffle + return array; + }); + + const [selectedResults, indices] = selectResults(pipelineResults, maxSamples, new Set()); + expect(selectedResults).toHaveLength(maxSamples); + expect(indices).toHaveLength(maxSamples); + expect(indices).toEqual([0, 1]); + expect(selectedResults).toEqual([pipelineResults[0], pipelineResults[1]]); + }); + + it('should return all results if maxSamples is greater than the number of pipelineResults', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + ]; + const maxSamples = 5; + + mockPartialShuffleArray.mockImplementation((array, numSamples) => { + // Mock implementation that does not actually shuffle + return array; + }); + + const [selectedResults, indices] = selectResults(pipelineResults, maxSamples, new Set()); + + expect(selectedResults).toHaveLength(pipelineResults.length); + expect(indices).toHaveLength(pipelineResults.length); + expect(indices).toEqual([0, 1]); + expect(selectedResults).toEqual(pipelineResults); + }); + + it('should call partialShuffleArray with correct arguments', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + { event: { category: ['3'] } }, + ]; + + selectResults(pipelineResults, 2, new Set()); + + expect(mockPartialShuffleArray).toHaveBeenCalledWith([0, 1], 0, 2); + }); + + it('should handle avoiding indices', () => { + const pipelineResults = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + { event: { category: ['3'] } }, + ] satisfies PipelineResult[]; + const maxSamples = 2; + + mockPartialShuffleArray.mockImplementation((array, numSamples) => { + // Mock implementation that does not actually shuffle + return array; + }); + + const [selectedResults, indices] = selectResults(pipelineResults, maxSamples, new Set()); + expect(selectedResults).toHaveLength(maxSamples); + expect(indices).toHaveLength(maxSamples); + expect(indices).toEqual([0, 1]); + expect(selectedResults).toEqual([pipelineResults[0], pipelineResults[1]]); + }); + + // Mock the partialShuffleArray function + jest.mock('../../../common', () => ({ + partialShuffleArray: jest.fn(), + })); + + describe('selectResults', () => { + beforeEach(() => { + mockPartialShuffleArray.mockClear(); + }); + + it('should return the correct number of samples and their indices', () => { + const pipelineResults = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + { event: { category: ['3'] } }, + ] satisfies PipelineResult[]; + const maxSamples = 2; + + mockPartialShuffleArray.mockImplementation((array, numSamples) => { + // Mock implementation that does not actually shuffle + return array; + }); + + const [selectedResults, indices] = selectResults(pipelineResults, maxSamples, new Set()); + expect(selectedResults).toHaveLength(maxSamples); + expect(indices).toHaveLength(maxSamples); + expect(indices).toEqual([0, 1]); + expect(selectedResults).toEqual([pipelineResults[0], pipelineResults[1]]); + }); + + it('should return all results if maxSamples is greater than the number of pipelineResults', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + ]; + const maxSamples = 5; + + mockPartialShuffleArray.mockImplementation((array, numSamples) => { + // Mock implementation that does not actually shuffle + return array; + }); + + const [selectedResults, indices] = selectResults(pipelineResults, maxSamples, new Set()); + + expect(selectedResults).toHaveLength(pipelineResults.length); + expect(indices).toHaveLength(pipelineResults.length); + expect(indices).toEqual([0, 1]); + expect(selectedResults).toEqual(pipelineResults); + }); + + it('should call partialShuffleArray with correct arguments', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + { event: { category: ['3'] } }, + ]; + + selectResults(pipelineResults, 2, new Set()); + + expect(mockPartialShuffleArray).toHaveBeenCalledWith([0, 1], 0, 2); + }); + + it('should handle avoiding indices', () => { + const pipelineResults = [ + { event: { category: ['1'] } }, + { event: { category: ['2'] } }, + { event: { category: ['3'] } }, + ] satisfies PipelineResult[]; + const maxSamples = 2; + + mockPartialShuffleArray.mockImplementation((array, numSamples) => { + // Mock implementation that does not actually shuffle + return array; + }); + + const [selectedResults, indices] = selectResults(pipelineResults, maxSamples, new Set([1])); + expect(selectedResults).toHaveLength(maxSamples); + expect(indices).toHaveLength(maxSamples); + expect(indices).toEqual([0, 2]); + expect(selectedResults).toEqual([pipelineResults[0], pipelineResults[2]]); + }); + }); + + describe('diffPipelineResults', () => { + it('should return an empty set if there are no differences', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['2'], type: ['type2'] } }, + ]; + const previousPipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['2'], type: ['type2'] } }, + ]; + + const result = diffCategorization(pipelineResults, previousPipelineResults); + expect(result).toEqual(new Set()); + }); + + it('should return a set of indices where the categories differ', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['2'], type: ['type2'] } }, + ]; + const previousPipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['3'], type: ['type2'] } }, + ]; + + const result = diffCategorization(pipelineResults, previousPipelineResults); + expect(result).toEqual(new Set([1])); + }); + + it('should return a set of indices where the types differ', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['2'], type: ['type2'] } }, + ]; + const previousPipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['2'], type: ['type3'] } }, + ]; + + const result = diffCategorization(pipelineResults, previousPipelineResults); + expect(result).toEqual(new Set([1])); + }); + + it('should return a set of indices where both categories and types differ', () => { + const pipelineResults: PipelineResult[] = [ + { event: { category: ['1'], type: ['type1'] } }, + { event: { category: ['2'], type: ['type2'] } }, + ]; + const previousPipelineResults: PipelineResult[] = [ + { event: { category: ['3'], type: ['type3'] } }, + { event: { category: ['4'], type: ['type4'] } }, + ]; + + const result = diffCategorization(pipelineResults, previousPipelineResults); + expect(result).toEqual(new Set([0, 1])); + }); + + describe('stringArraysEqual', () => { + it('should return true for equal arrays', () => { + const arr1 = ['a', 'b', 'c']; + const arr2 = ['a', 'b', 'c']; + expect(stringArraysEqual(arr1, arr2)).toBe(true); + }); + + it('should return false for arrays of different lengths', () => { + const arr1 = ['a', 'b', 'c']; + const arr2 = ['a', 'b']; + expect(stringArraysEqual(arr1, arr2)).toBe(false); + }); + + it('should return false for arrays with different elements', () => { + const arr1 = ['a', 'b', 'c']; + const arr2 = ['a', 'b', 'd']; + expect(stringArraysEqual(arr1, arr2)).toBe(false); + }); + + it('should return false for arrays with same elements in different order', () => { + const arr1 = ['a', 'b', 'c']; + const arr2 = ['c', 'b', 'a']; + expect(stringArraysEqual(arr1, arr2)).toBe(false); + }); + + it('should return true for empty arrays', () => { + const arr1: string[] = []; + const arr2: string[] = []; + expect(stringArraysEqual(arr1, arr2)).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/util.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/util.ts new file mode 100644 index 0000000000000..85dea9dd5a0c8 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/util.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PipelineResult } from './validate'; +import { partialShuffleArray } from '../../../common'; + +/** + * Selects a subset of results for further processing from the given list. + * + * The shuffle is deterministic and reproducible, based on the default seed. + * + * @param pipelineResults - An array of PipelineResult objects to select from. + * @param maxSamples - The maximum number of samples to select. + * @returns An array of PipelineResult objects, containing up to `maxSamples` elements and their indices. + */ +export function selectResults( + pipelineResults: PipelineResult[], + maxSamples: number, + avoidIndices: Set<number> +): [PipelineResult[], number[]] { + const numSamples = Math.min(pipelineResults.length, maxSamples); + const indices = Array.from({ length: pipelineResults.length }, (_, i) => i).filter( + (i) => !avoidIndices.has(i) + ); + if (indices.length < numSamples) { + const avoidIndicesList = Array.from(avoidIndices).sort(); + partialShuffleArray(avoidIndicesList, 0, numSamples - indices.length); + avoidIndicesList.length = numSamples - indices.length; + indices.push(...avoidIndicesList); + } + partialShuffleArray(indices, 0, numSamples); + indices.length = numSamples; + return [indices.map((i) => pipelineResults[i]), indices]; +} + +/** + * Converts a PipelineResult object into its categorization. + * + * @param {PipelineResult} result - The result object from the pipeline containing event details. + * @returns {string[]} An array of strings combining event categories and types. Returns an empty array if event, event.category, or event.type is missing. + */ +function toCategorization(result: PipelineResult): string[] { + const event = result?.event; + if (!event || !event.category || !event.type) { + return []; + } + return [...event.category.sort(), ...event.type.sort()]; +} + +/** + * Compares two arrays of strings for equality. + * + * @param arr1 - The first array of strings to compare. + * @param arr2 - The second array of strings to compare. + * @returns the equality predicate + */ +export function stringArraysEqual(arr1: string[], arr2: string[]): boolean { + return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); +} + +/** + * Compares two arrays of pipeline results and returns a set of indices where the categorization differs. + * + * @param pipelineResults - The current array of pipeline results. + * @param previousPipelineResults - The previous array of pipeline results to compare against. + * @returns A set of indices where the pipeline results differ in event category or type. + */ +export function diffCategorization( + pipelineResults: PipelineResult[], + previousPipelineResults: PipelineResult[] +): Set<number> { + const diff = Array.from({ length: pipelineResults.length }, (_, i) => i).filter((i) => { + const category1 = toCategorization(pipelineResults[i]); + const category2 = toCategorization(previousPipelineResults[i]); + return !stringArraysEqual(category1, category2); + }); + return new Set(diff); +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.ts index 6360f327521c5..3f84d188ebabf 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.ts @@ -10,12 +10,12 @@ import { ECS_EVENT_TYPES_PER_CATEGORY, EVENT_CATEGORIES, EVENT_TYPES } from './c import type { EventCategories } from './constants'; -interface Event { +export interface Event { type?: string[]; category?: string[]; } -interface PipelineResult { +export interface PipelineResult { event?: Event; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/kv/validate.ts b/x-pack/plugins/integration_assistant/server/graphs/kv/validate.ts index 6781f5cfa46d9..192c962599eba 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/kv/validate.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/kv/validate.ts @@ -93,7 +93,7 @@ export async function handleHeaderValidate({ async function verifyKVProcessor( kvProcessor: ESProcessorItem, - formattedSamples: string[], + samples: string[], client: IScopedClusterClient ): Promise<{ errors: object[] }> { // This processor removes the original message field in the output @@ -101,7 +101,7 @@ async function verifyKVProcessor( processors: [kvProcessor[0], createRemoveProcessor()], on_failure: [createPassthroughFailureProcessor()], }; - const { errors } = await testPipeline(formattedSamples, pipeline, client); + const { errors } = await testPipeline(samples, pipeline, client); return { errors }; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts index c0172f2d139d0..6d6b9714389c4 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts @@ -9,8 +9,7 @@ import type { LogFormatDetectionState } from '../../types'; import { LOG_FORMAT_DETECTION_PROMPT } from './prompts'; import type { LogDetectionNodeParams } from './types'; import { SamplesFormat } from '../../../common'; - -const MaxLogSamplesInPrompt = 5; +import { LOG_FORMAT_DETECTION_SAMPLE_ROWS } from '../../../common/constants'; export async function handleLogFormatDetection({ state, @@ -20,8 +19,8 @@ export async function handleLogFormatDetection({ const logFormatDetectionNode = LOG_FORMAT_DETECTION_PROMPT.pipe(model).pipe(outputParser); const samples = - state.logSamples.length > MaxLogSamplesInPrompt - ? state.logSamples.slice(0, MaxLogSamplesInPrompt) + state.logSamples.length > LOG_FORMAT_DETECTION_SAMPLE_ROWS + ? state.logSamples.slice(0, LOG_FORMAT_DETECTION_SAMPLE_ROWS) : state.logSamples; const logFormatDetectionResult = await logFormatDetectionNode.invoke({ diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts b/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts index be4b00852485c..e8dc44a152e80 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts @@ -10,7 +10,7 @@ import { StateGraph, END, START } from '@langchain/langgraph'; import { SamplesFormat } from '../../../common'; import type { RelatedState } from '../../types'; import { handleValidatePipeline } from '../../util/graph'; -import { formatSamples, prefixSamples } from '../../util/samples'; +import { prefixSamples } from '../../util/samples'; import { RELATED_ECS_FIELDS, RELATED_EXAMPLE_ANSWER } from './constants'; import { handleErrors } from './errors'; import { handleRelated } from './related'; @@ -30,10 +30,6 @@ const graphState: StateGraphArgs<RelatedState>['channels'] = { value: (x: string[], y?: string[]) => y ?? x, default: () => [], }, - formattedSamples: { - value: (x: string, y?: string) => y ?? x, - default: () => '', - }, hasTriedOnce: { value: (x: boolean, y?: boolean) => y ?? x, default: () => false, @@ -97,31 +93,22 @@ const graphState: StateGraphArgs<RelatedState>['channels'] = { }; function modelInput({ state }: RelatedBaseNodeParams): Partial<RelatedState> { - const initialPipeline = JSON.parse(JSON.stringify(state.currentPipeline)); - return { - exAnswer: JSON.stringify(RELATED_EXAMPLE_ANSWER, null, 2), - ecs: JSON.stringify(RELATED_ECS_FIELDS, null, 2), - samples: state.rawSamples, - initialPipeline, - finalized: false, - reviewed: false, - lastExecutedChain: 'modelInput', - }; -} + let samples: string[]; + if (state.samplesFormat.name === 'json' || state.samplesFormat.name === 'ndjson') { + samples = prefixSamples(state); + } else { + samples = state.rawSamples; + } -function modelJSONInput({ state }: RelatedBaseNodeParams): Partial<RelatedState> { - const samples = prefixSamples(state); - const formattedSamples = formatSamples(samples); const initialPipeline = JSON.parse(JSON.stringify(state.currentPipeline)); return { exAnswer: JSON.stringify(RELATED_EXAMPLE_ANSWER, null, 2), ecs: JSON.stringify(RELATED_ECS_FIELDS, null, 2), samples, - formattedSamples, initialPipeline, finalized: false, reviewed: false, - lastExecutedChain: 'modelJSONInput', + lastExecutedChain: 'modelInput', }; } @@ -143,13 +130,6 @@ function inputRouter({ state }: RelatedBaseNodeParams): string { return 'related'; } -function modelRouter({ state }: RelatedBaseNodeParams): string { - if (state.samplesFormat.name === 'json' || state.samplesFormat.name === 'ndjson') { - return 'modelJSONInput'; - } - return 'modelInput'; -} - function chainRouter({ state }: RelatedBaseNodeParams): string { if (Object.keys(state.currentProcessors).length === 0) { if (state.hasTriedOnce || state.reviewed) { @@ -172,7 +152,6 @@ function chainRouter({ state }: RelatedBaseNodeParams): string { export async function getRelatedGraph({ client, model }: RelatedGraphParams) { const workflow = new StateGraph({ channels: graphState }) .addNode('modelInput', (state: RelatedState) => modelInput({ state })) - .addNode('modelJSONInput', (state: RelatedState) => modelJSONInput({ state })) .addNode('modelOutput', (state: RelatedState) => modelOutput({ state })) .addNode('handleRelated', (state: RelatedState) => handleRelated({ state, model })) .addNode('handleValidatePipeline', (state: RelatedState) => @@ -180,10 +159,7 @@ export async function getRelatedGraph({ client, model }: RelatedGraphParams) { ) .addNode('handleErrors', (state: RelatedState) => handleErrors({ state, model })) .addNode('handleReview', (state: RelatedState) => handleReview({ state, model })) - .addConditionalEdges(START, (state: RelatedState) => modelRouter({ state }), { - modelJSONInput: 'modelJSONInput', - modelInput: 'modelInput', // For Non JSON input samples - }) + .addEdge(START, 'modelInput') .addEdge('modelOutput', END) .addEdge('handleRelated', 'handleValidatePipeline') .addEdge('handleErrors', 'handleValidatePipeline') @@ -192,10 +168,6 @@ export async function getRelatedGraph({ client, model }: RelatedGraphParams) { related: 'handleRelated', validatePipeline: 'handleValidatePipeline', }) - .addConditionalEdges('modelJSONInput', (state: RelatedState) => inputRouter({ state }), { - related: 'handleRelated', - validatePipeline: 'handleValidatePipeline', - }) .addConditionalEdges( 'handleValidatePipeline', (state: RelatedState) => chainRouter({ state }), diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts index c437f6fc35546..77ce549f589f4 100644 --- a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts @@ -22,7 +22,7 @@ import { buildRouteValidationWithZod } from '../util/route_validation'; import { withAvailability } from './with_availability'; import { isErrorThatHandlesItsOwnResponse } from '../lib/errors'; import { handleCustomErrors } from './routes_util'; -import { GenerationErrorCode } from '../../common/constants'; +import { CATEGORIZATION_RECURSION_LIMIT, GenerationErrorCode } from '../../common/constants'; export function registerCategorizationRoutes( router: IRouter<IntegrationAssistantRouteHandlerContext> @@ -91,6 +91,7 @@ export function registerCategorizationRoutes( samplesFormat, }; const options = { + recursionLimit: CATEGORIZATION_RECURSION_LIMIT, callbacks: [ new APMTracer({ projectName: langSmithOptions?.projectName ?? 'default' }, logger), ...getLangSmithTracer({ ...langSmithOptions, logger }), diff --git a/x-pack/plugins/integration_assistant/server/types.ts b/x-pack/plugins/integration_assistant/server/types.ts index a8f0d86a925ba..df054c40a9ef3 100644 --- a/x-pack/plugins/integration_assistant/server/types.ts +++ b/x-pack/plugins/integration_assistant/server/types.ts @@ -42,7 +42,6 @@ export interface SimplifiedProcessors { export interface CategorizationState { rawSamples: string[]; samples: string[]; - formattedSamples: string; ecsTypes: string; ecsCategories: string; exAnswer: string; @@ -52,9 +51,11 @@ export interface CategorizationState { errors: object; previousError: string; pipelineResults: object[]; + previousPipelineResults: object[]; + lastReviewedSamples: number[]; // Filled when reviewing. + stableSamples: number[]; // Samples that did not change due to a review. + reviewCount: number; finalized: boolean; - reviewed: boolean; - hasTriedOnce: boolean; currentPipeline: object; currentProcessors: object[]; invalidCategorization: object[]; @@ -154,7 +155,6 @@ export interface UnstructuredLogState { export interface RelatedState { rawSamples: string[]; samples: string[]; - formattedSamples: string; ecs: string; exAnswer: string; packageName: string; diff --git a/x-pack/plugins/integration_assistant/server/util/graph.ts b/x-pack/plugins/integration_assistant/server/util/graph.ts index 53a7787263ce1..4ae231c8d372d 100644 --- a/x-pack/plugins/integration_assistant/server/util/graph.ts +++ b/x-pack/plugins/integration_assistant/server/util/graph.ts @@ -19,9 +19,11 @@ export async function handleValidatePipeline({ }: HandleValidateNodeParams): Promise<Partial<CategorizationState> | Partial<RelatedState>> { const previousError = JSON.stringify(state.errors, null, 2); const results = await testPipeline(state.rawSamples, state.currentPipeline, client); + return { errors: results.errors, previousError, + previousPipelineResults: state.pipelineResults, pipelineResults: results.pipelineResults, lastExecutedChain: 'validate_pipeline', }; diff --git a/x-pack/plugins/integration_assistant/server/util/pipeline.ts b/x-pack/plugins/integration_assistant/server/util/pipeline.ts index 5df0ad0ea4917..6eacb8b19b468 100644 --- a/x-pack/plugins/integration_assistant/server/util/pipeline.ts +++ b/x-pack/plugins/integration_assistant/server/util/pipeline.ts @@ -56,13 +56,13 @@ export async function testPipeline( export async function createJSONInput( processors: ESProcessorItem[], - formattedSamples: string[], + samples: string[], client: IScopedClusterClient ): Promise<{ pipelineResults: Array<{ [key: string]: unknown }>; errors: object[] }> { const pipeline = { processors: [...processors, createRemoveProcessor()], on_failure: [createPassthroughFailureProcessor()], }; - const { pipelineResults, errors } = await testPipeline(formattedSamples, pipeline, client); + const { pipelineResults, errors } = await testPipeline(samples, pipeline, client); return { pipelineResults, errors }; } diff --git a/x-pack/plugins/integration_assistant/server/util/samples.ts b/x-pack/plugins/integration_assistant/server/util/samples.ts index 6993e87a774e9..9f14f20816415 100644 --- a/x-pack/plugins/integration_assistant/server/util/samples.ts +++ b/x-pack/plugins/integration_assistant/server/util/samples.ts @@ -48,17 +48,6 @@ export function prefixSamples( return modifiedSamples; } -export function formatSamples(samples: string[]): string { - const formattedSamples: unknown[] = []; - - for (const sample of samples) { - const sampleObj = JSON.parse(sample); - formattedSamples.push(sampleObj); - } - - return JSON.stringify(formattedSamples, null, 2); -} - function determineType(value: unknown): string { if (typeof value === 'object' && value !== null) { if (Array.isArray(value)) { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 99e52c8d22234..281807b6db45f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -24767,7 +24767,6 @@ "xpack.integrationAssistant.step.dataStream.logsSample.errorNotArray": "Le fichier de logs exemple n'est pas un tableau", "xpack.integrationAssistant.step.dataStream.logsSample.errorNotObject": "Le fichier de logs exemple contient des entrées n’étant pas des objets", "xpack.integrationAssistant.step.dataStream.logsSample.label": "Logs", - "xpack.integrationAssistant.step.dataStream.logsSample.truncatedWarning": "L'échantillon de logs a été tronqué pour contenir {maxRows} lignes.", "xpack.integrationAssistant.step.dataStream.logsSample.warning": "Veuillez noter que ces données seront analysées par un outil d'IA tiers. Assurez-vous de respecter les directives de confidentialité et de sécurité lors de la sélection des données.", "xpack.integrationAssistant.step.dataStream.nameAlreadyExistsError": "Ce nom d'intégration est déjà utilisé. Veuillez choisir un autre nom.", "xpack.integrationAssistant.step.dataStream.noSpacesHelpText": "Les noms peuvent contenir uniquement des lettres minuscules, des chiffres et des traits de soulignement (_)", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 032f15409355c..19d3dfb274fa2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -24514,7 +24514,6 @@ "xpack.integrationAssistant.step.dataStream.logsSample.errorNotArray": "ログサンプルファイルは配列ではありません", "xpack.integrationAssistant.step.dataStream.logsSample.errorNotObject": "ログサンプルファイルには、オブジェクト以外のエントリが含まれています", "xpack.integrationAssistant.step.dataStream.logsSample.label": "ログ", - "xpack.integrationAssistant.step.dataStream.logsSample.truncatedWarning": "ログサンプルは{maxRows}行に切り詰められました。", "xpack.integrationAssistant.step.dataStream.logsSample.warning": "このデータは、サードパーティAIツールによって分析されます。データを選択するときには、プライバシーおよびセキュリティガイドラインに準拠していることを確認してください。", "xpack.integrationAssistant.step.dataStream.nameAlreadyExistsError": "この統合名はすでに使用中です。別の名前を選択してください。", "xpack.integrationAssistant.step.dataStream.noSpacesHelpText": "名前には、小文字、数字、アンダースコア(_)のみを使用できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ea9606d1c6e00..d9e89fe098903 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -24548,7 +24548,6 @@ "xpack.integrationAssistant.step.dataStream.logsSample.errorNotArray": "日志样例文件不是数组", "xpack.integrationAssistant.step.dataStream.logsSample.errorNotObject": "日志样例文件包含非对象条目", "xpack.integrationAssistant.step.dataStream.logsSample.label": "日志", - "xpack.integrationAssistant.step.dataStream.logsSample.truncatedWarning": "日志样例已被截短为 {maxRows} 行。", "xpack.integrationAssistant.step.dataStream.logsSample.warning": "请注意,此数据将由第三方 AI 工具进行分析。选择数据时,请确保遵循隐私和安全指引。", "xpack.integrationAssistant.step.dataStream.nameAlreadyExistsError": "此集成名称已在使用中。请选择其他名称。", "xpack.integrationAssistant.step.dataStream.noSpacesHelpText": "名称只能包含小写字母、数字和下划线 (_)", From 2132e7506dffec640b446e9f9decf091b2980f54 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:07:05 +0300 Subject: [PATCH 52/84] [Cloud Security] Update wiz version callout (#196316) --- .../cloud_posture_third_party_support_callout.test.tsx | 6 +++--- .../cloud_posture_third_party_support_callout.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.test.tsx index 7b238ef49fa2e..b0e5cda02bfdb 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.test.tsx @@ -28,14 +28,14 @@ describe('CloudPostureThirdPartySupportCallout', () => { render(<CloudPostureThirdPartySupportCallout packageInfo={mockPackageInfo} />); - expect(screen.getByText(/New! Starting from version 1.9/)).toBeInTheDocument(); + expect(screen.getByText(/New! Starting from version 2.0/)).toBeInTheDocument(); }); it('does not render callout when package is not wiz', () => { const nonWizPackageInfo = { name: 'other' } as PackageInfo; render(<CloudPostureThirdPartySupportCallout packageInfo={nonWizPackageInfo} />); - expect(screen.queryByText(/New! Starting from version 1.9/)).not.toBeInTheDocument(); + expect(screen.queryByText(/New! Starting from version 2.0/)).not.toBeInTheDocument(); }); it('does not render callout when it has been dismissed', () => { @@ -43,6 +43,6 @@ describe('CloudPostureThirdPartySupportCallout', () => { render(<CloudPostureThirdPartySupportCallout packageInfo={mockPackageInfo} />); - expect(screen.queryByText(/New! Starting from version 1.9/)).not.toBeInTheDocument(); + expect(screen.queryByText(/New! Starting from version 2.0/)).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.tsx index 6bd4197dc267e..cd0a11b726fdf 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.tsx @@ -33,7 +33,7 @@ export const CloudPostureThirdPartySupportCallout = ({ iconType="cheer" title={i18n.translate('xpack.fleet.epm.wizIntegration.newFeaturesCallout', { defaultMessage: - 'New! Starting from version 1.9, ingest vulnerability and misconfiguration findings from Wiz into Elastic. Leverage out-of-the-box contextual investigation and threat-hunting workflows.', + 'New! Starting from version 2.0, ingest vulnerability and misconfiguration findings from Wiz into Elastic. Leverage out-of-the-box contextual investigation and threat-hunting workflows.', })} /> <EuiSpacer size="s" /> From 07642611899034fd4d9ab8362b6303405871c055 Mon Sep 17 00:00:00 2001 From: Philippe Oberti <philippe.oberti@elastic.co> Date: Tue, 15 Oct 2024 19:09:22 +0200 Subject: [PATCH 53/84] [Security Solution][Notes] - fix incorrect get_notes api for documentIds and savedObjectIds query parameters and adding api integration tests (#196225) --- .../lib/timeline/routes/notes/get_notes.ts | 104 +++--- .../trial_license_complete_tier/helpers.ts | 40 +- .../trial_license_complete_tier/notes.ts | 343 +++++++++++++++++- 3 files changed, 440 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index 0f3440d8ed13a..925379baedad5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -9,6 +9,8 @@ import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; +import { nodeBuilder } from '@kbn/es-query'; import { timelineSavedObjectType } from '../../saved_object_mappings'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { MAX_UNASSOCIATED_NOTES, NOTE_URL } from '../../../../../common/constants'; @@ -43,78 +45,90 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter) => { uiSettings: { client: uiSettingsClient }, } = await frameworkRequest.context.core; const maxUnassociatedNotes = await uiSettingsClient.get<number>(MAX_UNASSOCIATED_NOTES); + + // if documentIds is provided, we will search for all the notes associated with the documentIds const documentIds = queryParams.documentIds ?? null; - const savedObjectIds = queryParams.savedObjectIds ?? null; if (documentIds != null) { + // search for multiple document ids (like retrieving all the notes for all the alerts within a table) if (Array.isArray(documentIds)) { - const docIdSearchString = documentIds?.join(' | '); - const options = { + const options: SavedObjectsFindOptions = { type: noteSavedObjectType, - search: docIdSearchString, + filter: nodeBuilder.or( + documentIds.map((documentId: string) => + nodeBuilder.is(`${noteSavedObjectType}.attributes.eventId`, documentId) + ) + ), page: 1, perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); const body: GetNotesResponse = res ?? {}; return response.ok({ body }); - } else { - const options = { - type: noteSavedObjectType, - search: documentIds, - page: 1, - perPage: maxUnassociatedNotes, - }; - const res = await getAllSavedNote(frameworkRequest, options); - return response.ok({ body: res ?? {} }); } - } else if (savedObjectIds != null) { + + // searching for all the notes associated with a specific document id + const options: SavedObjectsFindOptions = { + type: noteSavedObjectType, + filter: nodeBuilder.is(`${noteSavedObjectType}.attributes.eventId`, documentIds), + page: 1, + perPage: maxUnassociatedNotes, + }; + const res = await getAllSavedNote(frameworkRequest, options); + return response.ok({ body: res ?? {} }); + } + + // if savedObjectIds is provided, we will search for all the notes associated with the savedObjectIds + const savedObjectIds = queryParams.savedObjectIds ?? null; + if (savedObjectIds != null) { + // search for multiple saved object ids if (Array.isArray(savedObjectIds)) { - const soIdSearchString = savedObjectIds?.join(' | '); - const options = { + const options: SavedObjectsFindOptions = { type: noteSavedObjectType, - hasReference: { + hasReference: savedObjectIds.map((savedObjectId: string) => ({ type: timelineSavedObjectType, - id: soIdSearchString, - }, + id: savedObjectId, + })), page: 1, perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); const body: GetNotesResponse = res ?? {}; return response.ok({ body }); - } else { - const options = { - type: noteSavedObjectType, - hasReference: { - type: timelineSavedObjectType, - id: savedObjectIds, - }, - perPage: maxUnassociatedNotes, - }; - const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); } - } else { - const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10; - const page = queryParams?.page ? parseInt(queryParams.page, 10) : 1; - const search = queryParams?.search ?? undefined; - const sortField = queryParams?.sortField ?? undefined; - const sortOrder = (queryParams?.sortOrder as SortOrder) ?? undefined; - const filter = queryParams?.filter; - const options = { + + // searching for all the notes associated with a specific saved object id + const options: SavedObjectsFindOptions = { type: noteSavedObjectType, - perPage, - page, - search, - sortField, - sortOrder, - filter, + hasReference: { + type: timelineSavedObjectType, + id: savedObjectIds, + }, + perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); const body: GetNotesResponse = res ?? {}; return response.ok({ body }); } + + // retrieving all the notes following the query parameters + const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10; + const page = queryParams?.page ? parseInt(queryParams.page, 10) : 1; + const search = queryParams?.search ?? undefined; + const sortField = queryParams?.sortField ?? undefined; + const sortOrder = (queryParams?.sortOrder as SortOrder) ?? undefined; + const filter = queryParams?.filter; + const options: SavedObjectsFindOptions = { + type: noteSavedObjectType, + perPage, + page, + search, + sortField, + sortOrder, + filter, + }; + const res = await getAllSavedNote(frameworkRequest, options); + const body: GetNotesResponse = res ?? {}; + return response.ok({ body }); } catch (err) { const error = transformError(err); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts index 9f40373976c28..a5944dc8c6149 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts @@ -7,7 +7,11 @@ import type SuperTest from 'supertest'; import { v4 as uuidv4 } from 'uuid'; -import { TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline'; +import { BareNote, TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline'; +import { NOTE_URL } from '@kbn/security-solution-plugin/common/constants'; +import type { Client } from '@elastic/elasticsearch'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { noteSavedObjectType } from '@kbn/security-solution-plugin/server/lib/timeline/saved_object_mappings'; export const createBasicTimeline = async (supertest: SuperTest.Agent, titleToSaved: string) => await supertest @@ -38,3 +42,37 @@ export const createBasicTimelineTemplate = async ( timelineType: TimelineTypeEnum.template, }, }); + +export const deleteAllNotes = async (es: Client): Promise<void> => { + await es.deleteByQuery({ + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + q: `type:${noteSavedObjectType}`, + wait_for_completion: true, + refresh: true, + body: {}, + }); +}; + +export const createNote = async ( + supertest: SuperTest.Agent, + note: { + documentId?: string; + savedObjectId?: string; + user?: string; + text: string; + } +) => + await supertest + .patch(NOTE_URL) + .set('kbn-xsrf', 'true') + .send({ + note: { + eventId: note.documentId || '', + timelineId: note.savedObjectId || '', + created: Date.now(), + createdBy: note.user || 'elastic', + updated: Date.now(), + updatedBy: note.user || 'elastic', + note: note.text, + } as BareNote, + }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts index 666e36325fd7f..dabb453f80158 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts @@ -6,7 +6,9 @@ */ import expect from '@kbn/expect'; - +import { v4 as uuidv4 } from 'uuid'; +import { Note } from '@kbn/security-solution-plugin/common/api/timeline'; +import { createNote, deleteAllNotes } from './helpers'; import { FtrProviderContext } from '../../../../../api_integration/ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -14,6 +16,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('Note - Saved Objects', () => { + const es = getService('es'); + before(() => kibanaServer.savedObjects.cleanStandardList()); after(() => kibanaServer.savedObjects.cleanStandardList()); @@ -70,5 +74,342 @@ export default function ({ getService }: FtrProviderContext) { expect(responseToTest.body.data!.persistNote.note.version).to.not.be.eql(version); }); }); + + describe('get notes', () => { + beforeEach(async () => { + await deleteAllNotes(es); + }); + + const eventId1 = uuidv4(); + const eventId2 = uuidv4(); + const eventId3 = uuidv4(); + const timelineId1 = uuidv4(); + const timelineId2 = uuidv4(); + const timelineId3 = uuidv4(); + + it('should retrieve all the notes for a document id', async () => { + await Promise.all([ + createNote(supertest, { documentId: eventId1, text: 'associated with event-1 only' }), + createNote(supertest, { documentId: eventId2, text: 'associated with event-2 only' }), + createNote(supertest, { + savedObjectId: timelineId1, + text: 'associated with timeline-1 only', + }), + createNote(supertest, { + savedObjectId: timelineId2, + text: 'associated with timeline-2 only', + }), + createNote(supertest, { + documentId: eventId1, + savedObjectId: timelineId1, + text: 'associated with event-1 and timeline-1', + }), + createNote(supertest, { + documentId: eventId2, + savedObjectId: timelineId2, + text: 'associated with event-2 and timeline-2', + }), + createNote(supertest, { text: 'associated with nothing' }), + createNote(supertest, { + text: `associated with nothing but has ${eventId1} in the text`, + }), + ]); + + const response = await supertest + .get(`/api/note?documentIds=${eventId1}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(2); + notes.forEach((note: Note) => expect(note.eventId).to.be(eventId1)); + }); + + it('should retrieve all the notes for multiple document ids', async () => { + await Promise.all([ + createNote(supertest, { documentId: eventId1, text: 'associated with event-1 only' }), + createNote(supertest, { documentId: eventId2, text: 'associated with event-2 only' }), + createNote(supertest, { documentId: eventId3, text: 'associated with event-3 only' }), + createNote(supertest, { + savedObjectId: timelineId1, + text: 'associated with timeline-1 only', + }), + createNote(supertest, { + savedObjectId: timelineId2, + text: 'associated with timeline-2 only', + }), + createNote(supertest, { + savedObjectId: timelineId3, + text: 'associated with timeline-3 only', + }), + createNote(supertest, { + documentId: eventId1, + savedObjectId: timelineId1, + text: 'associated with event-1 and timeline-1', + }), + createNote(supertest, { + documentId: eventId2, + savedObjectId: timelineId2, + text: 'associated with event-2 and timeline-2', + }), + createNote(supertest, { + documentId: eventId3, + savedObjectId: timelineId3, + text: 'associated with event-3 and timeline-3', + }), + createNote(supertest, { text: 'associated with nothing' }), + createNote(supertest, { + text: `associated with nothing but has ${eventId1} in the text`, + }), + createNote(supertest, { + text: `associated with nothing but has ${eventId2} in the text`, + }), + createNote(supertest, { + text: `associated with nothing but has ${eventId3} in the text`, + }), + ]); + + const response = await supertest + .get(`/api/note?documentIds=${eventId1}&documentIds=${eventId2}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(4); + notes.forEach((note: Note) => { + expect(note.eventId).to.not.be(eventId3); + expect(note.eventId).to.not.be(''); + }); + }); + + it('should retrieve all the notes for a saved object id', async () => { + await Promise.all([ + createNote(supertest, { documentId: eventId1, text: 'associated with event-1 only' }), + createNote(supertest, { documentId: eventId2, text: 'associated with event-2 only' }), + createNote(supertest, { + savedObjectId: timelineId1, + text: 'associated with timeline-1 only', + }), + createNote(supertest, { + savedObjectId: timelineId2, + text: 'associated with timeline-2 only', + }), + createNote(supertest, { + documentId: eventId1, + savedObjectId: timelineId1, + text: 'associated with event-1 and timeline-1', + }), + createNote(supertest, { + documentId: eventId2, + savedObjectId: timelineId2, + text: 'associated with event-2 and timeline-2', + }), + createNote(supertest, { text: 'associated with nothing' }), + createNote(supertest, { + text: `associated with nothing but has ${timelineId1} in the text`, + }), + ]); + + const response = await supertest + .get(`/api/note?savedObjectIds=${timelineId1}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(2); + notes.forEach((note: Note) => expect(note.timelineId).to.be(timelineId1)); + }); + + it('should retrieve all the notes for multiple saved object ids', async () => { + await Promise.all([ + createNote(supertest, { documentId: eventId1, text: 'associated with event-1 only' }), + createNote(supertest, { documentId: eventId2, text: 'associated with event-2 only' }), + createNote(supertest, { documentId: eventId3, text: 'associated with event-3 only' }), + createNote(supertest, { + savedObjectId: timelineId1, + text: 'associated with timeline-1 only', + }), + createNote(supertest, { + savedObjectId: timelineId2, + text: 'associated with timeline-2 only', + }), + createNote(supertest, { + savedObjectId: timelineId3, + text: 'associated with timeline-3 only', + }), + createNote(supertest, { + documentId: eventId1, + savedObjectId: timelineId1, + text: 'associated with event-1 and timeline-1', + }), + createNote(supertest, { + documentId: eventId2, + savedObjectId: timelineId2, + text: 'associated with event-2 and timeline-2', + }), + createNote(supertest, { + documentId: eventId3, + savedObjectId: timelineId3, + text: 'associated with event-3 and timeline-3', + }), + createNote(supertest, { text: 'associated with nothing' }), + createNote(supertest, { + text: `associated with nothing but has ${timelineId1} in the text`, + }), + createNote(supertest, { + text: `associated with nothing but has ${timelineId2} in the text`, + }), + createNote(supertest, { + text: `associated with nothing but has ${timelineId3} in the text`, + }), + ]); + + const response = await supertest + .get(`/api/note?savedObjectIds=${timelineId1}&savedObjectIds=${timelineId2}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(4); + notes.forEach((note: Note) => { + expect(note.timelineId).to.not.be(timelineId3); + expect(note.timelineId).to.not.be(''); + }); + }); + + it('should retrieve all notes without any query params', async () => { + await Promise.all([ + createNote(supertest, { documentId: eventId1, text: 'associated with event-1 only' }), + createNote(supertest, { + savedObjectId: timelineId1, + text: 'associated with timeline-1 only', + }), + createNote(supertest, { + documentId: eventId1, + savedObjectId: timelineId1, + text: 'associated with event-1 and timeline-1', + }), + createNote(supertest, { text: 'associated with nothing' }), + ]); + + const response = await supertest + .get('/api/note') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount } = response.body; + + expect(totalCount).to.be(4); + }); + + it('should retrieve notes considering perPage query parameter', async () => { + await Promise.all([ + createNote(supertest, { text: 'first note' }), + createNote(supertest, { text: 'second note' }), + createNote(supertest, { text: 'third note' }), + ]); + + const response = await supertest + .get('/api/note?perPage=1') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(3); + expect(notes.length).to.be(1); + }); + + it('should retrieve considering page query parameter', async () => { + await createNote(supertest, { text: 'first note' }); + await createNote(supertest, { text: 'second note' }); + await createNote(supertest, { text: 'third note' }); + + const response = await supertest + .get('/api/note?perPage=1&page=2') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(3); + expect(notes.length).to.be(1); + expect(notes[0].note).to.be('second note'); + }); + + it('should retrieve considering search query parameter', async () => { + await Promise.all([ + createNote(supertest, { documentId: eventId1, text: 'associated with event-1 only' }), + createNote(supertest, { + savedObjectId: timelineId1, + text: 'associated with timeline-1 only', + }), + createNote(supertest, { + documentId: eventId1, + savedObjectId: timelineId1, + text: 'associated with event-1 and timeline-1', + }), + createNote(supertest, { text: 'associated with nothing' }), + ]); + + const response = await supertest + .get('/api/note?search=event') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount } = response.body; + + expect(totalCount).to.be(2); + }); + + // TODO why can't we sort on every field? (I tested for the note field (or a random field like abc) and the endpoint crashes) + it('should retrieve considering sortField query parameters', async () => { + await Promise.all([ + createNote(supertest, { documentId: '1', text: 'note 1' }), + createNote(supertest, { documentId: '2', text: 'note 2' }), + createNote(supertest, { documentId: '3', text: 'note 3' }), + ]); + + const response = await supertest + .get('/api/note?sortField=eventId') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(3); + expect(notes[0].eventId).to.be('1'); + expect(notes[1].eventId).to.be('2'); + expect(notes[2].eventId).to.be('3'); + }); + + it('should retrieve considering sortOrder query parameters', async () => { + await Promise.all([ + createNote(supertest, { documentId: '1', text: 'note 1' }), + createNote(supertest, { documentId: '2', text: 'note 2' }), + createNote(supertest, { documentId: '3', text: 'note 3' }), + ]); + + const response = await supertest + .get('/api/note?sortField=eventId&sortOrder=desc') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount, notes } = response.body; + + expect(totalCount).to.be(3); + expect(notes[0].eventId).to.be('3'); + expect(notes[1].eventId).to.be('2'); + expect(notes[2].eventId).to.be('1'); + }); + + // TODO should add more tests for the filter query parameter (I don't know how it's supposed to work) + + // TODO should add more tests for the MAX_UNASSOCIATED_NOTES advanced settings values + }); }); } From 9512f6c26fbac59b8b8d7390dc28da930e42f181 Mon Sep 17 00:00:00 2001 From: Jen Huang <its.jenetic@gmail.com> Date: Tue, 15 Oct 2024 10:18:41 -0700 Subject: [PATCH 54/84] [UII] Support content packages in UI (#195831) ## Summary Resolves #192484. This PR adds support for content packages in UI. When a package is of `type: content`: - `Content only` badge is shown on its card in Integrations list, and on header of its details page - `Add integration` button is replaced by `Install assets` button in header - References to agent policies are hidden - Package policy service throws error if attempting to create or bulk create policies for content packages <img width="1403" alt="image" src="https://github.com/user-attachments/assets/a82c310a-f849-4b68-b56c-ff6bb31cd6bf"> <img width="1401" alt="image" src="https://github.com/user-attachments/assets/63eb3982-9ec9-494f-a95a-2b8992a408ba"> ## How to test The only current content package is `kubernetes_otel`. You will need to bump up the max allowed spec version and search with beta (prerelease) packages enabled to find it: ``` xpack.fleet.internal.registry.spec.max: '3.4' ``` Test UI scenarios as above. The API can be tested by running: ``` POST kbn:/api/fleet/package_policies { "policy_ids": [ "" ], "package": { "name": "kubernetes_otel", "version": "0.0.2" }, "name": "kubernetes_otel-1", "description": "", "namespace": "", "inputs": {} } ``` ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../single_page_layout/hooks/form.tsx | 2 +- .../sections/epm/components/package_card.tsx | 23 ++++- .../sections/epm/screens/detail/index.tsx | 85 +++++++++++------- .../settings/confirm_package_install.tsx | 12 ++- .../detail/settings/install_button.tsx | 25 ++++-- .../epm/screens/detail/settings/settings.tsx | 32 ------- .../detail/settings/uninstall_button.tsx | 15 +++- .../sections/epm/screens/home/card_utils.tsx | 4 +- .../plugins/fleet/server/errors/handlers.ts | 4 + x-pack/plugins/fleet/server/errors/index.ts | 1 + .../services/package_policies/utils.test.ts | 35 ++++++-- .../server/services/package_policies/utils.ts | 21 ++++- .../fleet/server/services/package_policy.ts | 24 ++++- .../good_content/0.1.0/changelog.yml | 5 ++ .../good_content/0.1.0/docs/README.md | 1 + .../good_content/0.1.0/img/kibana-system.png | Bin 0 -> 205298 bytes .../good_content/0.1.0/img/system.svg | 1 + .../good_content/0.1.0/manifest.yml | 32 +++++++ .../good_content/0.1.0/validation.yml | 3 + .../apis/package_policy/create.ts | 18 ++++ 20 files changed, 252 insertions(+), 91 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/changelog.yml create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/docs/README.md create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/kibana-system.png create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/system.svg create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/manifest.yml create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/validation.yml diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 2bae962f48e7c..0c3f54d9e5dff 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -391,7 +391,7 @@ export function useOnSubmit({ // Check if agentless is configured in ESS and Serverless until Agentless API migrates to Serverless const isAgentlessConfigured = - isAgentlessAgentPolicy(createdPolicy) || isAgentlessPackagePolicy(data!.item); + isAgentlessAgentPolicy(createdPolicy) || (data && isAgentlessPackagePolicy(data.item)); // Removing this code will disabled the Save and Continue button. We need code below update form state and trigger correct modal depending on agent count if (hasFleetAddAgentsPrivileges && !isAgentlessConfigured) { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 31213e5f9554a..52a3a90ae641e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -57,6 +57,7 @@ export function PackageCard({ name, title, version, + type, icons, integration, url, @@ -78,7 +79,6 @@ export function PackageCard({ maxCardHeight, }: PackageCardProps) { let releaseBadge: React.ReactNode | null = null; - if (release && release !== 'ga') { releaseBadge = ( <EuiFlexItem grow={false}> @@ -108,7 +108,6 @@ export function PackageCard({ } let hasDeferredInstallationsBadge: React.ReactNode | null = null; - if (isReauthorizationRequired && showLabels) { hasDeferredInstallationsBadge = ( <EuiFlexItem grow={false}> @@ -127,7 +126,6 @@ export function PackageCard({ } let updateAvailableBadge: React.ReactNode | null = null; - if (isUpdateAvailable && showLabels) { updateAvailableBadge = ( <EuiFlexItem grow={false}> @@ -145,7 +143,6 @@ export function PackageCard({ } let collectionButton: React.ReactNode | null = null; - if (isCollectionCard) { collectionButton = ( <EuiFlexItem> @@ -163,6 +160,23 @@ export function PackageCard({ ); } + let contentBadge: React.ReactNode | null = null; + if (type === 'content') { + contentBadge = ( + <EuiFlexItem grow={false}> + <EuiSpacer size="xs" /> + <span> + <EuiBadge color="hollow"> + <FormattedMessage + id="xpack.fleet.packageCard.contentPackageLabel" + defaultMessage="Content only" + /> + </EuiBadge> + </span> + </EuiFlexItem> + ); + } + const { application } = useStartServices(); const isGuidedOnboardingActive = useIsGuidedOnboardingActive(name); @@ -235,6 +249,7 @@ export function PackageCard({ {showLabels && extraLabelsBadges ? extraLabelsBadges : null} {verifiedBadge} {updateAvailableBadge} + {contentBadge} {releaseBadge} {hasDeferredInstallationsBadge} {collectionButton} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 51f54fc26c9cb..9a707500bb03d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -90,6 +90,7 @@ import { Configs } from './configs'; import './index.scss'; import type { InstallPkgRouteOptions } from './utils/get_install_route_options'; +import { InstallButton } from './settings/install_button'; export type DetailViewPanelName = | 'overview' @@ -362,13 +363,23 @@ export function Detail() { </EuiFlexItem> <EuiFlexItem> <EuiFlexGroup gutterSize="xs"> - <EuiFlexItem grow={false}> - <EuiBadge color="default"> - {i18n.translate('xpack.fleet.epm.elasticAgentBadgeLabel', { - defaultMessage: 'Elastic Agent', - })} - </EuiBadge> - </EuiFlexItem> + {packageInfo?.type === 'content' ? ( + <EuiFlexItem grow={false}> + <EuiBadge color="default"> + {i18n.translate('xpack.fleet.epm.contentPackageBadgeLabel', { + defaultMessage: 'Content only', + })} + </EuiBadge> + </EuiFlexItem> + ) : ( + <EuiFlexItem grow={false}> + <EuiBadge color="default"> + {i18n.translate('xpack.fleet.epm.elasticAgentBadgeLabel', { + defaultMessage: 'Elastic Agent', + })} + </EuiBadge> + </EuiFlexItem> + )} {packageInfo?.release && packageInfo.release !== 'ga' ? ( <EuiFlexItem grow={false}> <HeaderReleaseBadge release={getPackageReleaseLabel(packageInfo.version)} /> @@ -520,7 +531,7 @@ export function Detail() { </EuiFlexGroup> ), }, - ...(isInstalled + ...(isInstalled && packageInfo.type !== 'content' ? [ { isDivider: true }, { @@ -532,31 +543,37 @@ export function Detail() { }, ] : []), - { isDivider: true }, - { - content: ( - <WithGuidedOnboardingTour - packageKey={pkgkey} - tourType={'addIntegrationButton'} - isTourVisible={isOverviewPage && isGuidedOnboardingActive} - tourOffset={10} - > - <AddIntegrationButton - userCanInstallPackages={userCanInstallPackages} - href={getHref('add_integration_to_policy', { - pkgkey, - ...(integration ? { integration } : {}), - ...(agentPolicyIdFromContext - ? { agentPolicyId: agentPolicyIdFromContext } - : {}), - })} - missingSecurityConfiguration={missingSecurityConfiguration} - packageName={integrationInfo?.title || packageInfo.title} - onClick={handleAddIntegrationPolicyClick} - /> - </WithGuidedOnboardingTour> - ), - }, + ...(packageInfo.type === 'content' + ? !isInstalled + ? [{ isDivider: true }, { content: <InstallButton {...packageInfo} /> }] + : [] // if content package is already installed, don't show install button in header + : [ + { isDivider: true }, + { + content: ( + <WithGuidedOnboardingTour + packageKey={pkgkey} + tourType={'addIntegrationButton'} + isTourVisible={isOverviewPage && isGuidedOnboardingActive} + tourOffset={10} + > + <AddIntegrationButton + userCanInstallPackages={userCanInstallPackages} + href={getHref('add_integration_to_policy', { + pkgkey, + ...(integration ? { integration } : {}), + ...(agentPolicyIdFromContext + ? { agentPolicyId: agentPolicyIdFromContext } + : {}), + })} + missingSecurityConfiguration={missingSecurityConfiguration} + packageName={integrationInfo?.title || packageInfo.title} + onClick={handleAddIntegrationPolicyClick} + /> + </WithGuidedOnboardingTour> + ), + }, + ]), ].map((item, index) => ( <EuiFlexItem grow={false} key={index} data-test-subj={item['data-test-subj']}> {item.isDivider ?? false ? ( @@ -619,7 +636,7 @@ export function Detail() { }, ]; - if (canReadIntegrationPolicies && isInstalled) { + if (canReadIntegrationPolicies && isInstalled && packageInfo.type !== 'content') { tabs.push({ id: 'policies', name: ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/confirm_package_install.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/confirm_package_install.tsx index 31e4fc32233e9..5fdcdc49223e1 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/confirm_package_install.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/confirm_package_install.tsx @@ -14,9 +14,13 @@ interface ConfirmPackageInstallProps { onConfirm: () => void; packageName: string; numOfAssets: number; + numOfTransformAssets: number; } + +import { TransformInstallWithCurrentUserPermissionCallout } from '../../../../../../../components/transform_install_as_current_user_callout'; + export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => { - const { onCancel, onConfirm, packageName, numOfAssets } = props; + const { onCancel, onConfirm, packageName, numOfAssets, numOfTransformAssets } = props; return ( <EuiConfirmModal title={ @@ -53,6 +57,12 @@ export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => { /> } /> + {numOfTransformAssets > 0 ? ( + <> + <EuiSpacer size="m" /> + <TransformInstallWithCurrentUserPermissionCallout count={numOfTransformAssets} /> + </> + ) : null} <EuiSpacer size="l" /> <p> <FormattedMessage diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx index 28ad351b865f7..6348d1bf6cfab 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx @@ -13,23 +13,37 @@ import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../. import { InstallStatus } from '../../../../../types'; import { useAuthz, useGetPackageInstallStatus, useInstallPackage } from '../../../../../hooks'; +import { getNumTransformAssets } from '../../../../../../../components/transform_install_as_current_user_callout'; + import { ConfirmPackageInstall } from './confirm_package_install'; -type InstallationButtonProps = Pick<PackageInfo, 'name' | 'title' | 'version'> & { + +type InstallationButtonProps = Pick<PackageInfo, 'name' | 'title' | 'version' | 'assets'> & { disabled?: boolean; dryRunData?: UpgradePackagePolicyDryRunResponse | null; isUpgradingPackagePolicies?: boolean; latestVersion?: string; - numOfAssets: number; packagePolicyIds?: string[]; setIsUpgradingPackagePolicies?: React.Dispatch<React.SetStateAction<boolean>>; }; export function InstallButton(props: InstallationButtonProps) { - const { name, numOfAssets, title, version } = props; + const { name, title, version, assets } = props; + const canInstallPackages = useAuthz().integrations.installPackages; const installPackage = useInstallPackage(); const getPackageInstallStatus = useGetPackageInstallStatus(); const { status: installationStatus } = getPackageInstallStatus(name); + const numOfAssets = Object.entries(assets).reduce( + (acc, [serviceName, serviceNameValue]) => + acc + + Object.entries(serviceNameValue || {}).reduce( + (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, + 0 + ), + 0 + ); + const numOfTransformAssets = getNumTransformAssets(assets); + const isInstalling = installationStatus === InstallStatus.installing; const [isInstallModalVisible, setIsInstallModalVisible] = useState<boolean>(false); const toggleInstallModal = useCallback(() => { @@ -44,6 +58,7 @@ export function InstallButton(props: InstallationButtonProps) { const installModal = ( <ConfirmPackageInstall numOfAssets={numOfAssets} + numOfTransformAssets={numOfTransformAssets} packageName={title} onCancel={toggleInstallModal} onConfirm={handleClickInstall} @@ -61,7 +76,7 @@ export function InstallButton(props: InstallationButtonProps) { {isInstalling ? ( <FormattedMessage id="xpack.fleet.integrations.installPackage.installingPackageButtonLabel" - defaultMessage="Installing {title} assets" + defaultMessage="Installing {title}" values={{ title, }} @@ -69,7 +84,7 @@ export function InstallButton(props: InstallationButtonProps) { ) : ( <FormattedMessage id="xpack.fleet.integrations.installPackage.installPackageButtonLabel" - defaultMessage="Install {title} assets" + defaultMessage="Install {title}" values={{ title, }} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index bffa043a8fa1c..51119dabf4cf9 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -23,11 +23,6 @@ import { import { i18n } from '@kbn/i18n'; -import { - getNumTransformAssets, - TransformInstallWithCurrentUserPermissionCallout, -} from '../../../../../../../components/transform_install_as_current_user_callout'; - import type { FleetStartServices } from '../../../../../../../plugin'; import type { PackageInfo, PackageMetadata } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; @@ -238,22 +233,6 @@ export const SettingsPage: React.FC<Props> = memo( const isUpdating = installationStatus === InstallStatus.installing && installedVersion; - const { numOfAssets, numTransformAssets } = useMemo( - () => ({ - numTransformAssets: getNumTransformAssets(packageInfo.assets), - numOfAssets: Object.entries(packageInfo.assets).reduce( - (acc, [serviceName, serviceNameValue]) => - acc + - Object.entries(serviceNameValue || {}).reduce( - (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, - 0 - ), - 0 - ), - }), - [packageInfo.assets] - ); - return ( <> <EuiFlexGroup alignItems="flexStart"> @@ -365,15 +344,6 @@ export const SettingsPage: React.FC<Props> = memo( </h4> </EuiTitle> <EuiSpacer size="s" /> - - {numTransformAssets > 0 ? ( - <> - <TransformInstallWithCurrentUserPermissionCallout - count={numTransformAssets} - /> - <EuiSpacer size="s" /> - </> - ) : null} <p> <FormattedMessage id="xpack.fleet.integrations.settings.packageInstallDescription" @@ -388,7 +358,6 @@ export const SettingsPage: React.FC<Props> = memo( <p> <InstallButton {...packageInfo} - numOfAssets={numOfAssets} disabled={packageMetadata?.has_policies} /> </p> @@ -418,7 +387,6 @@ export const SettingsPage: React.FC<Props> = memo( <div> <UninstallButton {...packageInfo} - numOfAssets={numOfAssets} latestVersion={latestVersion} disabled={packageMetadata?.has_policies} /> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx index df472c765c09a..aba40aeba2397 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx @@ -16,17 +16,16 @@ import { useAuthz, useGetPackageInstallStatus, useUninstallPackage } from '../.. import { ConfirmPackageUninstall } from './confirm_package_uninstall'; -interface UninstallButtonProps extends Pick<PackageInfo, 'name' | 'title' | 'version'> { +interface UninstallButtonProps extends Pick<PackageInfo, 'name' | 'title' | 'version' | 'assets'> { disabled?: boolean; latestVersion?: string; - numOfAssets: number; } export const UninstallButton: React.FunctionComponent<UninstallButtonProps> = ({ disabled = false, latestVersion, name, - numOfAssets, + assets, title, version, }) => { @@ -38,6 +37,16 @@ export const UninstallButton: React.FunctionComponent<UninstallButtonProps> = ({ const [isUninstallModalVisible, setIsUninstallModalVisible] = useState<boolean>(false); + const numOfAssets = Object.entries(assets).reduce( + (acc, [serviceName, serviceNameValue]) => + acc + + Object.entries(serviceNameValue || {}).reduce( + (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, + 0 + ), + 0 + ); + const handleClickUninstall = useCallback(() => { uninstallPackage({ name, version, title, redirectToVersion: latestVersion ?? version }); setIsUninstallModalVisible(false); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx index 5a97d1c61df6f..19f4d8740b75d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx @@ -65,6 +65,7 @@ export interface IntegrationCardItem { titleLineClamp?: number; url: string; version: string; + type?: string; } export const mapToCard = ({ @@ -114,7 +115,7 @@ export const mapToCard = ({ const release: IntegrationCardReleaseLabel = getPackageReleaseLabel(version); let extraLabelsBadges: React.ReactNode[] | undefined; - if (item.type === 'integration') { + if (item.type === 'integration' || item.type === 'content') { extraLabelsBadges = getIntegrationLabels(item); } @@ -128,6 +129,7 @@ export const mapToCard = ({ integration: 'integration' in item ? item.integration || '' : '', name: 'name' in item ? item.name : item.id, version, + type: item.type, release, categories: ((item.categories || []) as string[]).filter((c: string) => !!c), isReauthorizationRequired, diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index d8971948397d3..31e4b9d6704c7 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -45,6 +45,7 @@ import { PackageSavedObjectConflictError, FleetTooManyRequestsError, AgentlessPolicyExistsRequestError, + PackagePolicyContentPackageError, } from '.'; type IngestErrorHandler = ( @@ -84,6 +85,9 @@ const getHTTPResponseCode = (error: FleetError): number => { if (error instanceof PackagePolicyRequestError) { return 400; } + if (error instanceof PackagePolicyContentPackageError) { + return 400; + } // Unauthorized if (error instanceof FleetUnauthorizedError) { return 403; diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 6782b8122a552..de528f082c096 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -73,6 +73,7 @@ export class BundledPackageLocationNotFoundError extends FleetError {} export class PackagePolicyRequestError extends FleetError {} export class PackagePolicyMultipleAgentPoliciesError extends FleetError {} export class PackagePolicyOutputError extends FleetError {} +export class PackagePolicyContentPackageError extends FleetError {} export class EnrollmentKeyNameExistsError extends FleetError {} export class HostedAgentPolicyRestrictionRelatedError extends FleetError { diff --git a/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts b/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts index 9d68dde10a13e..7075990620ef5 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts @@ -153,16 +153,41 @@ describe('Package Policy Utils', () => { ).rejects.toThrowError('Output type "kafka" is not usable with package "apm"'); }); - it('should not throw if valid license and valid output_id is provided', async () => { + it('should throw if content package is being used', async () => { jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); jest .spyOn(outputService, 'get') .mockResolvedValueOnce({ id: 'es-output', type: 'elasticsearch' } as any); await expect( - preflightCheckPackagePolicy(soClient, { - ...testPolicy, - output_id: 'es-output', - }) + preflightCheckPackagePolicy( + soClient, + { + ...testPolicy, + output_id: 'es-output', + }, + { + type: 'content', + } + ) + ).rejects.toThrowError('Cannot create policy for content only packages'); + }); + + it('should not throw if valid license and valid output_id is provided and is not content package', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + jest + .spyOn(outputService, 'get') + .mockResolvedValueOnce({ id: 'es-output', type: 'elasticsearch' } as any); + await expect( + preflightCheckPackagePolicy( + soClient, + { + ...testPolicy, + output_id: 'es-output', + }, + { + type: 'integration', + } + ) ).resolves.not.toThrow(); }); }); diff --git a/x-pack/plugins/fleet/server/services/package_policies/utils.ts b/x-pack/plugins/fleet/server/services/package_policies/utils.ts index 5c19345a58f79..ef59c643a8b35 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/utils.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/utils.ts @@ -13,8 +13,17 @@ import { LICENCE_FOR_MULTIPLE_AGENT_POLICIES, } from '../../../common/constants'; import { getAllowedOutputTypesForIntegration } from '../../../common/services/output_helpers'; -import type { PackagePolicy, NewPackagePolicy, PackagePolicySOAttributes } from '../../types'; -import { PackagePolicyMultipleAgentPoliciesError, PackagePolicyOutputError } from '../../errors'; +import type { + PackagePolicy, + NewPackagePolicy, + PackagePolicySOAttributes, + PackageInfo, +} from '../../types'; +import { + PackagePolicyMultipleAgentPoliciesError, + PackagePolicyOutputError, + PackagePolicyContentPackageError, +} from '../../errors'; import { licenseService } from '../license'; import { outputService } from '../output'; import { appContextService } from '../app_context'; @@ -35,8 +44,14 @@ export const mapPackagePolicySavedObjectToPackagePolicy = ({ export async function preflightCheckPackagePolicy( soClient: SavedObjectsClientContract, - packagePolicy: PackagePolicy | NewPackagePolicy + packagePolicy: PackagePolicy | NewPackagePolicy, + packageInfo?: Pick<PackageInfo, 'type'> ) { + // Package policies cannot be created for content type packages + if (packageInfo?.type === 'content') { + throw new PackagePolicyContentPackageError('Cannot create policy for content only packages'); + } + // If package policy has multiple agent policies IDs, or no agent policies (orphaned integration policy) // check if user can use multiple agent policies feature const { canUseReusablePolicies, errorMessage: canUseMultipleAgentPoliciesErrorMessage } = diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 86d81f3df9b1a..0cf4345235d54 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -233,6 +233,17 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } const savedObjectType = await getPackagePolicySavedObjectType(); + const basePkgInfo = + options?.packageInfo ?? + (packagePolicy.package + ? await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + pkgVersion: packagePolicy.package.version, + ignoreUnverified: true, + prerelease: true, + }) + : undefined); auditLoggingService.writeCustomSoAuditLog({ action: 'create', @@ -245,7 +256,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { logger.debug(`Creating new package policy`); this.keepPolicyIdInSync(packagePolicy); - await preflightCheckPackagePolicy(soClient, packagePolicy); + await preflightCheckPackagePolicy(soClient, packagePolicy, basePkgInfo); let enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', @@ -448,6 +459,15 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }> { const savedObjectType = await getPackagePolicySavedObjectType(); for (const packagePolicy of packagePolicies) { + const basePkgInfo = packagePolicy.package + ? await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + pkgVersion: packagePolicy.package.version, + ignoreUnverified: true, + prerelease: true, + }) + : undefined; if (!packagePolicy.id) { packagePolicy.id = SavedObjectsUtils.generateId(); } @@ -458,7 +478,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }); this.keepPolicyIdInSync(packagePolicy); - await preflightCheckPackagePolicy(soClient, packagePolicy); + await preflightCheckPackagePolicy(soClient, packagePolicy, basePkgInfo); } const agentPolicyIds = new Set(packagePolicies.flatMap((pkgPolicy) => pkgPolicy.policy_ids)); diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/changelog.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/changelog.yml new file mode 100644 index 0000000000000..c8397a8b6082d --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/changelog.yml @@ -0,0 +1,5 @@ +- version: 0.1.0 + changes: + - description: Initial release + type: enhancement + link: https://github.com/elastic/package-spec/pull/777 diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/docs/README.md new file mode 100644 index 0000000000000..3a6090d840af5 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/docs/README.md @@ -0,0 +1 @@ +# Reference package of content type diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/kibana-system.png b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/kibana-system.png new file mode 100644 index 0000000000000000000000000000000000000000..8741a5662417f189d8c87388583c4aa8ff3a6e5a GIT binary patch literal 205298 zcmcG0WmH_t5-uSGLU2fM*93QWcXyZI?hXMG+}+(_a0Zva;O@@g?(RJ9eea%g<(!{y zt-WS?&(yA6(_P(N_0_j0L|#@5;S=^J2nYxS32|XX2nZ+;1jKvgkI-+QXrG_6y}iA2 zR1_10s2IiFhk)RRkPsG7c6)b}0pq4Da@!03$t)?jhpZ_mwTG8LEK?v66PZoW1I@aI z7Go%+jO;1&`sr)1xWc|jKJ0m(tDvCNS6D(=QqpLvgV*7e_Jx(7-{Z2|FS!R+xweOg zPi<Ho!90!?IM6b~U0`Non#VO;P6xy^DDfYEbz~QRY{XFMJ4*ViFaLQ(s6WJq>fG#6 zUWva{@B=C|{7ryZXe{z?1^z#42o3M-3B#y%IbI|_%);A^;&*alRt0D#(2?NZTa5IQ z@<aZ`amGfR4XT>R6WctPDQmRfgvR$gs)odQ4nd<p#>Ng^X>siy8$-HX^`zr;+WS$e zQiiUc_3G4p&|GrpdVgW8-Q@5=j_=tiv+(Kk3|Xq(zu2*8gb%|oI+j9vHiB4{R;Y5j zsGLd0^j!Vn2KCA9pzvFfJkn%0z0V+{RoK9YbR4oHFLXmXqI}R^zqoK|)}An+@MMSV zxrsqf*J#GcHnTer;V}nMDbGisFUK;+(L(E!^DceG1bg~$wV`a_N$Zn|6Y0(I5(S@_ zjAM%<)rSRQE>F)k5i$IUu}w4fT-%*5`G}T*uAOo*B{$7>8oJX5A(&@7S?<83sYgnS zYfN|z58knx%Q^WV8Kb4m2%V%$(?Gx<L-^tXHQlVJMVB_)H?f#He=l4<r)Pn8sT>0^ zN{_0Om)h3dECz~#gU<TRrk;f-DlWB!*G#CpqDmj5yxFnwVXgOdf8Gk+rzV^fj(GB- zPAtmg;FU4<4Mad2nASDViFdXm;Mi4sv`M1rp=TNV`4fsOz*%P273>{k&>gsbImS2l zps+E|b=n&6^8C2j>G%GItGavot_c+{Ep?N}WxC>e9;c+kv;q7Z%*V#U@?~IPU{SdJ zZUfHi`FhShRk?y2q$-CWN3rQ{?R3;-EUi_;TRBo&KP7@MI8MC8*2Lw5>qZw?UmgSS zsbErMwBuyyHn{#gk!w?6%1KQ(?+^cz2<>`9X`G|8&s?p#h3|L1oZqcadSGm%MPVlB zXL}S8u1&w$9X28@R&X)PCcT+Uyh_+&oT|QgnsxHig(CGq&6jKEkVc>Wq0-0|w1&^1 z=AN!=#&E<(HEWUW6CU{yF3+((bfm7c1r&Z&cm4;p{d;4Wv+u#CH-jA8d$a~KfmC~| zm0ymW5|k_{wkPgN<p|0h(4IM|prmf;guKfTQLEhCaE5tzBFY`M*U&MFMocGO0A>~{ zV-gtXA|VXbjRi{nnzR*ZA}w5;(;1?m(_Q<^Yv5T+USQy0;B5kSLEE<j)XH_m8cLef zcubtIk@XO_Jnw{DEC^4scc_H_JibCga+c#wpw(`a&&ANu!1ZhrwafMyHN7YvCZmZo zM%r_Z3sOpR-|#FVyz(Ccw410wJx%L3a8!0XAs$~3oOOLraJ6_*y1@@CgVHAZbN2dD z&ydXoH3x&+6eS8Bxu=BB^^DkK#h09jFN}nboH{4>#4Ft>nZ-gu)8gSN+!`lsi+PDz zbc%Veoez%A<CQ``CZzpZ7efsOnU0!vGpVB6(P=b)BJn*TBXOTW6v|~G`uh56x40mL zgoGrs+Y|Rj-~?5+KYSWVV{147<0~0`czy=H;4i(jMI-t~dc#(>H7dU7ru&{?TEF1w zm8n%mx?k=X3@0-LM@0pp_9!<i$yIB^8vcUaOQikcIQzQ3NL#OwD0I<+Bp4SsnOnVC z#5ZBylO;}j5;TGEH1bRa=9lDL+tlh!FN&PpK*`Dvh>9Zc3dLYcFm^|uid3@E{=l6- zJf~gw$%}issjiL4T1v$~>iSjen^YoWOb?IO+NqW%OSP;^U!E}gj4iYxY5@Q{u7_Rm zJMK$QTU`@pQkSxt#C**eyX18y>@H#&apd=zG4kHKLB2xy`@ZiP2L@_;;&&m6F$WpO zI{6v<c1qp|8^=X6)#Ynt(+CQn>mHv4KH-eS5FHeU)*gfz(pXab-o1UtGiy^We{~B? zlQG1;e)lx`>iU-eH45--r<@L{7y%vhJJAOP@83&WJK3i+=ks#OFOu#~s{Y9;j(wLZ zj&lR)TTCeJ+X$Pi^Q>ce=&PJAxRs(b0%fZmrR1vLSS)x<2mqmUKx+}0iEHKNZ{WeN zFNWGUd5Pvv#xX??vssd0a<ksh9kmsi5{jZ7hJ|lT*wJb&h13;ztqJox#yWIMtr6as zcM{fo)`}K{8vUikwv_~FA}Q4=2Hb4MM~QO->g5&HH@tOqmbBCJ#-4l{@s`h$1igp0 z-}8I-4#@-?bv~j)yNUm6BRGp9StFAI?}|WpGNwTk#w7$DV-HyTPL@KKou%uoPb{yF z7-Gy*SaJ?C$NO%|bJ8kJjr~}y?TEwWFi?iLWZD`KufVr4{WH-ctW<8OQh{d99dXtH zaV~f;mlfvlIuB^#q(4M9g-XJZQ)y{9|3ic7%tNl5CqA5vb3)96*Qf#Kxp50!#BE7x zhsEs1!PNN1YWtfUV8~giiq9~4__q&Z_4Um}yTjapJ4<)FPRrJdAl)La4g(~e>5FEz ze=-k))nvZJ4NS(^^3|1-*Zp?V;!XeCUWJ5@k1x+qSty<2{|gSo!f-U2Na*ai0h|iL zHA>R`{rY<%?{z{}DX(2<$Y%B9m-b$&bnNS*I8P#5?q**$=zcKp8`eqf5g2VCD<DHl z^ibOLwsv&zJCw`Itn}vKuE0vO^CeB_inITG(BTWem!Kl=#Mbz7EhQ)KECCLmPUuMo zJ}3{Nxs!tYGm^IpN}7y}x1F5@A9GU2*d^C2p<P~2V3*>T7;ZpVuzHUjIWfF<Aub48 zsHH-HrRt^x=)oYl*IsS@zL%QbR!jRyU2MNHRF#X!ifWPeL5jNxzC==F*)0c-*?n}u zP4y+bGt9^B_5nd!!d@tYT*lr*-QMf==9QjuV8l$u6VJlgWS7*1PnDu^a{<O;>DzE- z0uEAzGK$Tj^BfVgSHA9#I{G-wc0_MzIe~Pd(;O_mjc1>TIwrb?xMZe#g`YO;6&a?+ zSD@C{5kZ`8umh-9jegI#c6TjYBxi3<+53lX*OLI!+U7^Hn!PDyjbM0$=0JPvEaWjd zHtvb`kR6?R@z$~$cNyapKI+!ztI)~9>WDMLlwTixcX<PPpvw20=y<VfKZTV61hOzX z*j#>vL!av3mMhocv?1Ft{=AXl6*k%bcIUWD-^h_#=@4QwgQ0N|Meo#YKBDTe1bf%I z>mHu<61EC-1B__4q3{F@Uotvd?D9_~!{7T5SA3(5({c`aO!8e@K{UQ5BZfq@;ekS; zdqkT_;)}#c%3{$d<ugJ^yeYHdoFgFB-@J-(bFXh&t+Q<~62~!?8(fWLgu^M7c{bl5 zV+Wlf=^BfNE_5KTh;>x*F+7c0n0=PQ)8^9<FBAoI2lG~%Wrk?h4kN0&ufZwD`8J%K zcWl(7R2p_GJol-atWZ=v^kK+Ly>xqcx)T}fCt)&}@<fKJ<w@SkKBBy`3BT&rdJ_~n zdm{J_V+DWGlri1B|3Kq;+(RWqLa((0sJQgYc(%7Xcf{oJjGEPa9fG-jR8PN%{#VGl zrYAtCqUG}aRg%-t2j*%poem%8p*|R$R(RHbB<c0rhQ`UPN93%C#W7m-If4cS&TO>c z1KGkk{j_OBhVu}8KBs1uB-av4tU16&y(KZ$`LS1p;%ZLOg?3o;S*T*J)b#>-eBc3) zYy6&U8EdrOc~$bzS$D-RYol+D)7)QrxWabad)c&b)_i5PH<P1ny=cS$UbB4?Hf#80 zq4iSG6}9pPFIdIiLkvdAJMM!T9;yE0mGL-&x)pkNym^Y2N&)i1s@V#6ND5<xaiG2V ztti*ls<@4s@?ErQo3CJ0VV{iUdO4#|)j@;#qNci5k=*GwOSk)3omh*<fMqon7Qtx; zohzx`!Vsn#fKF@%wN8Bo^^OA@ce(7AN4WXzC-Q-3jeQv-dXLPGWv)oUxDOj&&$^;E zoDA-?7}$pA6r`oYznLU6=5u#{l7v^fi6Y>O)M~IX*CI*4G)bdWF8(2#!D;X|_vJTI z+Bg%fnNcR<*N=3?f%;Kq5lWT#oxW-Dg3~~xnw1v(r+tH3H>_H8K#N=thE`N+I+Y?x zyI7guA+u?+MNH%=_|}&jGjBI2P4qf?8K%AR<UQUqKtrx;nwAgdMwmfPPVQK9x3}Wx zceCf@t87*u@BO!QW%vyIXt|&HpS`)OKdCU}={cSg<Aj!D5qkyi&aTx{7=KkrZef6B zdUO^{r1WfJkwvsLL-)w{?(nm&uenqBQbl$F>BC`)+_~jVfd3vheO_qrapR096SI9! zE?rqmM=5fh0j0Zn8Lj;Ea$D5x_CjTu={4Ht0kMxnW~x9^nmacwknp}9pEz(v3?wHB zPxY%R=}|%t$)H6biauP}c53vLNei81$Q3j7AOV7l1=n-{nx`?uGVZ}filV0GVItP; zo@nR>hhAkLw-?dp!|jMBN$9P@&k7%mj<%bxv*w=t*D|i4+dhwwZD%Jidh7Gqr*Q21 zS`$6*Jt_vP*;0AH@xg8LM*Vu_TKa4`<YIfPI9T>(V+>CTk}2y6f%8-|xov~&IBTV$ z1+>F}!!TUNDtgrzahhosRx$+C=vlGgvf*r<^)iSwM{mGjs_--vO^Qvv?Sj0@YS6*y z<6X#VyIF}mLpX}sj(gAI_dRL&`0g}!jVrCP5e^*xVG1^?s7mC3V5J_pO^B~EOi8iI z?9ivW4ed09o%KBJy$8LlkLQQgfc85$xjLNi0<EZhBV@DdG6N7*ebqyrHMUT~mxfc% z^kFwB9jc=V5dHP<qKcjhgt9Xm3@rxf)#sr;{mIJTcdCY6cwc}+;KQhfK6sF>eY0f5 zLP$~)*_u{BL{D5m{XJbBN_Vse=BC)#@9tVP{j95c-|pRyF}IN%9F0DT2H=LV&Hf*! z^$hqc4RB?u(ld0J%0>-&jT5FZCYO5^1$)j1uk^+N_m|zz)6iGwr+h3O)_koYE4F;4 zQ4!?1*&YNyFU$5>m#jKG+OU2xUd9?1ZhUvNZos_9a4QEkj>~cPyQa|+6h3pqd1`m0 zl6Kb7=O}jzZsLE1%Xexh^7w`pT=AMr>J?FKZB#k2#_XSHwEHtS;<+?2O6c+uFj>u4 zMKzX=x>ZAlrs>2xHh;UUY#dv8ACwPQx#+kSHW-WmW=mn{%?DkkIH#O37ICcmuDVA) z<k9onR7GKKU}!D6@&Vtiz#nou%h`MMz^U5?`)SeU^X$+p6$6H*n`<u$&DF6o$t}g| zUm|<@PDzIrSwBDC(8bb|661#-2W?yP{nYG!8%tDgR@vXab9%s|QI@`v33aSpKXW1M z-L6b8EWRBSY8#dzCtGIIc>>I-7cL=b&ZbibI597_OU=gJYqG+6yXk|8n!wZ0gadr> z6RI8VR@`p#=fp6dOL>v@1lK6N&<HJ8gTq+>s+7n38|(@V(1v}ov)jkSlSwdm7*9#+ zkbmlB%L9|8vy+#=(7~>h{HOVC2rYWIa?!!5;%h@mmeEKm%Qei0E3KYSN+k*;ZB-55 z2T-4`rsP!HN*KSuNcF(9Y;JAoer1@&^Ev1i$0a>lY0=zQe1~@?*~IMCxE*i)@e#c( zq|RnjUhf2$vC;wK><c?o_bB|#Gj7XNba<L|`~pZBZh7Lj7OB6=R^BkRnbIK%ekRj3 zsO{k@r;lq0N^+w3J+EAp>DPFhDH+U1;h9Qeo8!QbUTPU|F6MY2Yx=P0J&*44?yC|G zQ9Y$OnZ*z>H>F`65u(PvM?Hgv&DEyL8GV9q<H2(7yHquKEuyeI^NxqT2xhCWa186c zShoas!}MUgMOSfSYy&Gkf^<%A6}oW_+$a+bpL?T73oY%1$UcL6PbKdey1eoPkv@a# zv+U~9?zxUClWQ7gpC{hF>TX<M0%WD+B46pOOlr3C;q2lVj9A<jU(3VhP^Kc?YmWP2 zT}fiW^F*JC3O~49x|}=rH7Ji4S<kZ|igz~2^@9_^BYJ3tZm8XTYt%vm|D#DBw_!a{ z<K6B16E1s2w5e7U;^)VnQo=%pGeirj#4CczRp^Fq)>I}yBKN$<BYNu0XYSEquXj9) z4ZT0G((Ggo?paSLiKh&xnW03Z79Z-#Z|W*E@vQ|X&n~41jKYdDR()oWKW~+3+|2Si z!7o8<E0MEZdT)e`dQ_5jJebkB9JaEe%M6v>8k_&Dvp|f>Pzdz(Sb(;TA=09}wFP;i zS*#tOcTcoCqqR2azWU8JK3;xx*^xO4Rh=>ByYHh~za9Lje2u`5=3XiLX!fCNn;=nd zhH#<9_Ijdt2K?FsYb|YETFT%~()d`5NxiW&`n72^WTJo2DvIzvQL<o-9I5L#brDfN zLy>IW*G*N4dm=CMM&_s5%;Wj&7Ga~#WV>L(U}UA<Od-u0P3}%E0HyI8IP=3V<ryn8 zp8kedv<KjH7F-9fx9z54d9^E^@{zw!AfReH^$N7~o6`$O_NgQWwN<&7siKm1Hm#w~ z+g`wz)QiS2*JS?O?dmzP@0Q6kuk=iD^?~8hH?e(HU@qE0)1JR&x6YRmbvGvD&!|VV zv&DkE9cKzwCUtMF7oc_BsjHFsHLB=|T<kEnf1GSl^Ltgf=d{}7_V$2tp>f6$dzE$r zyH*w_6;q`5#bE%vy}MMNbC#c7SmY4!hG}+W%##0BWB#tYrP%R+g?ly+4;#S?N9+oY zg`q6Y`Tnrxkcj`Vi><1}MGnRC&SAC1%exMdvS5X&7!Yi|<s+p1NxB=W={x75@^yl9 zx@J{YPp*-!TcbAzX(<emczMm}b1X{pfx6%D5OZ)kqA<4>`g+J*pEDB}r|ph*ncEm9 zEGD4*S056v0X-3OFie)P&6LYtSN>LPPNDsh@bgmr%$jtx(yseg411e}i=M@sdV-y? zq{o&+L*XDJaC)L=2t)B{QAdY4+YLe*U2lYU2~8fq$Pf)(FEK_NGz^T<$#T<Ih1{Pe z_B+EV>OiZMuyRv2G*r}F<G^40w*d*%8j7B!Tg3`_(g|{z+~0^&ypS<5%?5|A{iZyL z@)ABFp6W!fOnaL)q<Gtz^db>wNucifHI+#j4x-UGwr0^_OhOB+&ee6)r>&~6#3eTH ztNL2*AnJJJ8`hK*$>=r`YDVPqqI!BBOqVpBbd$`nz%_2bpt4abxubtEZ1#}#_S}T| zLPKqMEcx?iDUzfsH~Lx#6+`Y93$}+B%!>=}vzL9s=3rNhswLcTJ$DP0YIq^KjTGdF zI4h&Y!i@kdB@YGwoeacO{}4iZwY093nmo{{k*LF#6F2H}VHmPnTlj*MOwDvZ3~Qa; z<;7VYWzvS#{!PlrJY7t;u5Cd_%$O=sQMfY7sVGfnwS9#2s==yH!H=(E2s3@SXXjdR z@LE$_r1{_QOS9X5PX^f`b&#LF+e8<jYa7r1GANe+5NJ6%v;*+`eeGdZF%?n+sE{Lg zW*zQ5_Q-ZRBLA*pR%DyFX=}@Ta!#5!AU8ar$KVAb<(?CUoVzUxVX%eSTxWX$;ph-_ z9$-;S9B;}_X0ds^R(;5H<m>(Udxhn+7VDCa6_##xg(Iu76lbmMg<~%cC@;zKiIY+e z4+o0X`%7O(Tba2QYD-k2?~gKY=bjpLuEg`R8|j{p%tsIFLw6@UhYwT4t>+3!9@t0s zI6DR>!%6gweemCB55w;D+De=Bms}=m%{9WP`Ix_~+dLKsyYY;_!&?qoOED*^A!B(D zNA4TQIpxFJRGWq!!k@Yi9-<V@aByZLH=U!i8#f%LtN$3+i`0c{epcn?+(s*ym{v6~ z2=7-gu2JtJ^d)vI8Wh3KtxZzg@riN!BCt`Ha#Wyn#O?d~PeTe8f#H$;M~R1#JxYS7 zUZ42%qz}Fe4;C?G-U}GNMgVTYe_$7Kq}gFENf}US^aT<U8U%-?#h{XpTyZRwfk&Sg z2OR2XYZ}le*v8sz6sRx~jw<^y5(d0$>fk6V^%|&f+Eos_kLmJ65;hk<kLM@-EZ+Oo zkbAoUK*>AXQv%qZ6$RD#<x8zOUoMtGrX~)JpX6L=eYjrdWeiB`cNtZj|B6%?tCE{I zjk^fd{~Ri!IA;(Ca^tks?r^W?uN>W`KGITl`JE%Hb(+S($^7j+?GQ%2RjRc#FJy^D zTS~cBeh&C;Fpa3wfu_8@_;#5chfXZz^b*_E4Kyh>VbI{Jn~m{xcdQ;Ew=!T+aJkm} z$1jOWNbCR+3|T~#GY_)R2s|$I23v688#IT_?N$}1>P}^`SobYd>gZghOx0ZAIziqj z#b&wD?|7*p&v+<NUhC<EdfSQadtG9S=bQr|Q*_GEyPCy;Gq4xXhq9o+EY%|r?r@Pp zsO1r%6%l|F7WXsD3o{tfz^iZZQHv>ZP^D6o;ppl9GF76-tv4LY&J}}(B5A(@8Kip5 z=NEVJeE?EQvau$^+yj$$ktpe#IINT@y&9-03Ew7&<M_(QfXn1Hm?Hc=C4R6^9^^Lg zmeYlLk@)vrRCe5Ns&boehY|BTr}$S9TuIemZ~K3kCt655JOa{5wpF#IV3TYIEbUQ7 zMh$_r=i)R=J_-Fd&#)mWGo8y>$Le*m)IJ$c?ktQra>6{DEwj1QY(bKpC2M7`E+UVC zJ>%kS_s8VcRK{q-dN1!S7F5L}=3WK@m8&qNOHrw>B`8i<2jNWbFM=X8dQq*gx$u(+ zSo<DBm)sEWz^sv7iuyI?7LM5CTPUr3ZjgbXgGYp;C&uL>8I)VFlNZ&iXcBf7W-+Mf z{B%eTQgD|q#8lMhmnwzHyn5Ue?oJuLBB7pa6T5p}0%+O8aM1!}@k%X~Gqpv*aD7ft zRqvF3W3B1g=7v;RYJIk`%y+gi;;7DDNb$l{MkU+ss|P$UPqK9z_A{_Gv~8piEFI`< zE6uh=!|Rm_o1VT%uCwCO#A(eh4;R;dcYEg?+)F>@tTv~V1+ugT)*ez^?w>;e2jt>G z0eeg~%*}ONpU#nlRTmyi3{*w|9qtLf!xw<?9_)7^)enux(dUis8c`Gs4^`}5vlt9? zMd#uj+ZGxjKBZu{^Zu!UZZhd-ow(N*=|4(HubhyQ5XA>tx~);%EOHJ~5Y-bK2DUNb zWkg}+xQNG<VWpOqDCcH1+F_W~3l&=F068U+@4_09a|ag3nycHEbGU`nJr*Mm7wPZR zIfz9cYm@+OgENe9JcItcE@`;M(fFv3H+cwR6DnGly8cVh_oz$BN5cX-LFSi95>Ez3 z1&|&Ib~I5evOQF%R(+d^4C%PeyDq2{Mv4>Dd(?!hR$|6MBc%0d!ZwuFm4Izcb=cba zBD&5CA{IOiSM&BIy&Ld|#ru&95(Wo41N@-p)WCJO6xAtOysQES&)j5Gd1ypjGYuMW zA0>IR2js?XmTv`*fucK20%ZY)k&4|jhOXN?$<H_(4&O5$9DL}$8%+Lgvt$XQjFuPL z?288li=?gj9#T!a8@GY15<93UZfhBEcJ;Ss#Hxv`QdKeREvu`i1iN`@%&m59M{0(i z@bx-;bS~kCqA&}4qdoVjJ4jr{c`qstlN*d10u(WfsTinr@XtMtDL0Hp*Uuus=i_B! z3|gR~sy@vWE(zrmvVq2CQzp)ymb^^W*zhwRPZVdFd+P*IeT0J5LQVD+i-e=sSf2(w z?m+9^c+FB!9U5vK80~iEI>@z6(@g;=C#%^Ab8dc7*Ps0C<_$n}K7<M8r^v#kT)>NJ z?|*3EOC6ZQFRc_p)q*!ygZI{$tKpR5ip(O#bU%F;0jPny08v%FU)=mCD!Y{5bEqiC z0}F0IN`g;Ki0xWUZN}W4CLtHezc-jndn*?~32r$u6t7IxT-#C>+Q>81Ovh2v8i0A* zV4_(~*siy*%*nxoZEWV1Zo&J%SSo**e5IgmjbYdrwSGz+WtZ4D3OI6)43GSh#?~Wb zg=a!{DFTM*=2HioXyR6?^>rYuWS^rN@v}^u8%4$!u^((b%d?0BUz&Q%eebpfJ-Uw{ zc(FQO1|m2{)HEC)kOWksu{k6>*hp?AxywEpP{MER&i6*00kjyP;-{F-tXV!khtkC8 zKX(|dwOAwMj<Bq|ey$F!*W-ZtncwvsYpqwa=TX2lI+{*ieI75K{Ns5D+(XICKgEA- z;3Hv3<Cdc~m0CkX_;Gb(5f3ZPTU1v7fU@BNrANz?t&;~T2A>}siGLrM)lhP<d*0}} z-7w2<cBw&WRQ;i<Q^cE0R$@zvS0uTpIzb0apSu$|)(pnF82^>D2qUPIgkOW_a?s#w zaXwpjJIr*rcB@ELJHD;YUCOuQ6QGGOzM4jLTOyJw3#sN)(UV4X=erweI)?iHs#=78 zxunt0mgcs4a&7}R<s=pW(!)_}$0B0wpBY>dZ$P2Iv1sZXPmNh<BEYxzy8gJiXW?O5 zBgH;_LCvEV6{G#&tvpwQfI3T}skO8k&g*|4r%L#|&VQ*%6LjKq5aztz>U$qFPl#`r zIADQDtQcXQ<li7Rm?#ZPlAGCyPjfhC7lzrFXnpB=6k$`@=4WW2G+{Lv<$x4hJVM8* zD0nk}V~Uxyzbc+wp2?x=qQh;zSfcM~Jz}I}c?YZsB=~hLX6#*hl94MX>D7ek_(hH2 z3m3&@$ZTeAF(`A-%-NKj%RaN5O6z>*z$lc%8!Tm(i0|_eOn_K_%~JH3^@Iri8j95* zxAQGfjTZWRT1n1))y#6PLL>+gkKPdU@#=U9gU93Q2dV?_{TAA?;}}m<ReW3M=Ao^| z+<9A>F^%)GMKow!mjPjlExxlB^+2=$mk(_j1Sfw&;p|}B?)4x%)3rrZxTy6ED1pZ) zd@JsPdV0*!FE_AEa6<v$c!rK~U9et59`b`?CK0UJ**dF|UR<hbU(LJYwMuu2+28Xn zeJC}XgsVveZg*>W*fxO_vIllsx3!q5PNUN<J{`bC)xM8Pk@x)W7T@^jj6BK{DKZ~T z%GM=fPwtGB)u!hj;Cqj%)y}7v#pB&+6w6jwim`8aSB1IW@SHs3!@0GbTcd$`JhSr! z(KZ0~OY%{UF!$qIB+Q2rH-jB_kXWVnTcASMZ~}w!)=KPKS#A@^iF)v|4`+9M1|^S7 zH|d2S&SPVsGyrg&K-T+v9NKfd@>y)r-K4FrYUdSy5{})kt;*g9H!vy@Qv-Tx|F$hk z2p}|m(-Wm&d`4QQ$0QNIcY(<x(EdTwvUuyu&jD|*a|iI~BAc$%?W;aAa0`j~8+;sv z9E<XFhm+?zQ|n@FDA5?sv1lu_)g6Q_B4fCMq^vJD<WwZq7UI6`BxFFJ)(GOB+Bq7$ zd53P8p7UAKyU|_CncBvTq$EI<s>DaNkp&&DZyOlWK(~iN!?UiwvI(iM#{&3+fWykv z?I0omg5wmKp<mVVwuySM@lB^%8Yk=_b+N5O_~|--2$-I2ha>#ha^Al*lf2F=^JZ&o zde3ok6|dTn@3&vmq>OeWQ0MNRV9Q+MLKscy!s3TJ-!MOcy|e8ra4R$dgTkphq`uS^ zHuYfZfh_X@o22lRDW&<%Ztm=zFJvavt45{%*N+VN##==1)l6>$4`Cl}7a`n}yGa(= ztWX`Tr}{9#$L4jf%6zZQ0lb~iLT9q3n+nhKH)Z%?t!_IK@B~wM79JLl`=EB8LW;3c z)_>cq5g`)~zYbx+xTFQZN_lQn(;n6zHGB7Fi#55yzGlzH&mPhpvP3L|<~he;JQ!V5 z#9WXzt=^5wdmofO@6iq6zLfwkpzIs*T~$r+17{RiA5{z7c_eyX8pzjG;E2kkm-f|f zH7Q22d3ecB-moX#ti_re+aR@kmy{EuaW$Y4r!29ecPm15B|?I_v6Rfr>SrIKLZ&%9 zw+~2hFk$xw7sUg6OGb$UXKfdCx37ohoC)t)DrD|X_cGCMMf0k>GPoP1BooH<hgM^y z^l#}H_-rUeqoN;)g%rKTi`OB=Q5SoSN`4wJTq?;KzP{CpppdeH8ltY^)Vm%$Lz>(A zhOX~APcR`{S|bMu<OGvs_>avk&n(}0Hhfb!DywW4G&B49RwFlcm9FSlUSd*dJ=SS) zF&N9_DON5)eYidhdPDvts1LHWoeewzDkMwXLx;8xp3xp}sj#(YVh7%}et;{tNopqR z{?TQw>Qd3xs%v&DpPEHta|C`+bjBl`GE^;Yjp6O}oSxAV(Aw>!Y4^k|Ex!Pd4L)^G zeTIHoo5wxe7$*tOx7HYG_J}Xj)Kg>V=#^o6K|hrUBF6Omh&(;PxY9a~1YdtZA`s$^ zRpdaqmWJ&<Gmu87m(qQoiOxFwnZq+-%_BgIH>^mFPV~@Wd+QtG{iQF1hoy#1J+mtv zWv%QBf*Bl?W5tWOUg3+xJUUg{%~C&xut&for6*&g2v!|U6kJBQtem@1fK^DH6BP@v zdM>NYp1mx~TNuc}36iY3%m28*csGoHeBR(;%W~*3`tUJB7tV+ggXAgWu^gMj(nv+2 zy{NjKobzeEkpnog>TfFzNu2$FrDB61D?=}8QB`Ny=;Fd&rg_t74k$2k=PG6s1|H@m z?4C*S^&OpJ%up9Gdd|%mxCYv<D&Pe5s{g@SI+G(^vpS9`yXVxw;Lx*NSe%RMc}ZQ; zNC9L(a<Prhj5hQ2@zw)x=6oX;(QndKd=FNIlBTfdI*H^grAfNeAMBudMDCkREj`E9 zbKgA;jci4}k_}re<2CTEhP$??4u}rNAkcIKgY_1mJz=m!c8FZ04Fk8u;9CMXcG^pM zh=%7EHBzc0yXOoed+29+W30vQ5g+Xhhcwcb<Bjiw)s|V@OZ8lj&BG+m)xfe_EF7== zt~X;t_9;whu9_}NK;gnd{NBduLwoR&B>+UvvddM!TkaDHPo~>>M=MlE;Y3^((0y?8 zAg}ecGV1}>NG++WrA*Vr(y~$pD5)vxa?(zO6+BPiH-BTiy(ELTLF}@D=1x5DBZ@lU zOiOdfrn^*`SNyop%cqkw%}96XlVQ4xHDlZ#I2nOR=fWb18k%!6twZ~4P5r4A_2{70 z@#p!bsivKAOZ<OyTEsJl`nxoW<e;Wx?A;>3fcOhixZ}veWJX(%Bd`xU^oP<*O#+Lm z;G{6X4SlGF)3AWS28h5Z#)`YW#N1&i7bc9{Le{z4uSAyeA_@ii<#AKJiRyX`j&{1U zw!)#=QC{&o-Sl?f_#Q*P;BePHVdTmSb$);|r%RWQkX+wYrE@kqGH;u++}QDZ^?WQ_ z%CoyK*y@+pdK<xca)#~aqa}S>*l39VJopt*#M@dCbsk!&Jn6}pM?*D=6Y>PLlu2lk zeZsyEZE1BpMS2&F?RtvRf*DgzQ6~QD`ae!PS7iAKtecU=?jNhCXi>U9+4moX{;T`` zi}bffAe=4#p}GS#_Ut}!<(1GY{nLM1^`};F1b@yaLjv*TD(Mh{-aqh*Km7V%4G|GS zHGg#uFzQ#WQsMUAJEJ|K^mqy>#+W};{a1ru0kwZdiF|1mt`6Hs(*t@V)`o=k9DT#K zXssESODe`!r%$d0k+V!O+t{+ph~D|q9yh8<H~V><{jW`ZO8dy|?7gJJw35nVnJbsY zYc^f15NR}+KxkswGnI!)`S@tTS4z#nf}pm6W`A?w%mG|Qb(aE)w|CeC0YU`1z+O9) z_|L`!JGbV3t3>b4Vyc<`&iJjUA4~CB7p9#(fUzvTs4?D0cr*%GVLQ7jrNgF0#{Fsk z&*--o!ty;eZrS#D%m9rKK0R$RGCs@BA2E7_%8Mtrz>$W>RY1(?V$Pnb+~$ve$K3oB z(%I~M=x{VZ=AUd#$1<2C&<9;FLV79acBQ9Y``T!1pF=><j2RRu@a0pRp35K9n17AR z|I2sf@1SV48>5YiXfZ(HJe0Q8oC!mD4(`52v)~t&Y?F+RXI%DwmB0QkXY7yHI|?*$ zS=mv0j}PR*c#)x^-4+Q|Cj|<7v#man?+rfuD;WQutG~iSejpAOkr=K*tua<CP~=^` zyF2G~GqStwN=AD8L;e3WilDS<gI6RaL_ACiRf<k*Y%phmSSC?>)(K;1e}6M8Nc@VE zv&9k8?<OV-ES|p4gxuguA1~kwQp>ih+Kzdv(k9VQs|fyAP7m9L7E3Q&guihjZ`jXt zy0|*cP67bUdih*o*kb*=oSd9ROM$V$0{{7$JK_97(|J5cO;P9#)VOo~&E`gcR8K2S z%kgZBC!=MyLbvj=|I!K*Y2?qpn0rU4!F>rzb8AX%=ucr7wZ%mDyA|DfP%s2x+G+%% z<=TM_ld0=L0@vEssW&e<Q2ze@XTwZmonXCJ`d;{nh<N(h93hx;jaq0NR;#b$DJ@n* z`HPe($+?c~^LsdKF)0OL$%O_LAtwvvIfIp4>hX+c=mYau60jH6*v~J0CnHJ>t)kX$ zKGl_9gL>NkV2S?8Deh$Oi+9{>UW3>XA=jLXd7=a#^$esFG@GuR$c`y8R<SL#2MlMV z8a{iWHX?_T<;2`GD^`uPwY8mZ#mRNOF>T+tcfCJh(axSO$2M!nc)Q;idlQv<e9UI! z+59l;i~`iG=ZW2b*lLM}Y$>UR)P9ieZJtjArK-DYcZlmu`<>B{1$-H11%l~4mx7y1 zH+K3Fnj0H@l+pjV2`ZuA5fnGKf|kS3e4_YEKvmPVT)W>Lj#^Bt36*}&;pH)`7UI&p zOY!J&i7n6_OFV`FL_t1}X_sDEx%WNXPSs>VriOLQZK0>IfH|yM10N@C-hr`(K5?nF zkaU$_vSz-{y69k`w(=r^RmPgSH-E7HyJy>vcn}q15>D@@#lsky-E%YHR@|j&xiE|! zM%JOln_eTjv6=|D$kUcn-gqiC#E$11oLPWY&{Bh~kb5v5pAW8{*ZD7(<rdf9y*aJ} zi398?-BusyNC<R1nynIAt-0$xGQOUJGIh_W7>(h!b>h4zb7eE2s1F@J1*~EQ&9k+4 zeNz@*I@mr<C2}DA1Iqfdp)b`bxmQIqJ-S)FnHI6<G^#ycD+5>VnUC*epfzqd!SWL; zG_$4X9q-|s@F;ErgH|<fsQMM0U=23Oq5X`mq6gH=<pI(1rMhdlCE?O0YLvmOXCDLE z3UY3L9&Rky1DQAWHFRr?bw{lv7>CZuuj8YY6j(RJ_Q|V?thbfv`1k}sUvGp5fa}|w z3mxgGIPUf34et^Z8V&X!JL@j_jO%4hpX!M()IBG=7Bm-_>u&G;>(Vlbh`tU!7MyS{ zp$b||_z24qr~9Oi!M&JGY<I%e=<SFDXHlRZzo9j(bXCl3mBEggLACcmLT4WOj#^SU z`AP+>59S4%xUEkMINbsw%I$A4MLqDO^j3cEBc!fh@n95Gy0ywvzRx1Z!b$`+&3SZt zuP`!m6+DE!t>GW10bqzC{M&9d+ZQAwrD39+tIeCP$y-+aVljvsa@TlPtcw?Q%2OoY zWw(YRJG#bvpj2u8#;=9KFbukl$pWcj-8RnUm6e|R!H3@w!0StoOj`kI>jWoKH)9jy z^%~F@$9T#F+FuICTFcJoAe+GBPSX^fD$^@cQp0zemAw8j2rZnc(3gKP7uQZGkUf>r zy2FWOo_@sz-vVg;RWPALFlWoG;nMeLHs(uc&NPq%M`;dnd|_d%(?@2f@SK%IkQocF zl(iF$yy0uzsGsFQRf^m8?%JNpH;s{0Y?9(BTfUTr>Io2}QHNhQ<|V>?cRyxoq5UH8 zI4nq^VMw{Vyw7Z;%g)zLUUY+sssjW(*vR(kbu&6FI(xmElcK94bt3F{^W9UoKiJXv z{7CxNuBn1!+x&Uq(<dZvY1WkzFf+qZl7h~ecv~oAU=~V^8O4dGD+FYnj6HE6nHp%j zgl7FTF0_Ejr%!8%WZYfeJH;E?#TG-Zg3=G&F!}pZ8##*tnL3c0wwY8*=X7E&iYGFM z__NJ1CcMt1h<S3zVf`VoyBU>?IPU8xEO*0Ad2kxiS_?_qnTxy-N*A^y56jJWrvqK2 zq%>xs?TwOoqDYC1L|n1ghGzZ#FlYMH<J#oU9v_&7aAR+SxPZLiHvS%ZEM#u(D+lFf zc7+8gO1&8czLJs;o5IGMZ05F@k(ZN9jP@+QtJj=q7zT27lP5|;DO?(?Gp;PpplJ0{ zgRmL<1M|A=JU{i&Z(eUu{m-`236B5ai&ZRRW|U+r<|fVkWH~LSpRzHY!4<_43vLB+ z<;wortPwMp7De~rKt*6Ta}<N3*0EQlHOzFQq`{wlm*CwaQ|hLoj2In}NvYSGky*AL zcIUYMv12E2l!dXH{V2AbYC`6K*__ZYIV&AQ=&bF_mI_c!vVH1Y3z3-&J4{4bbNLy+ zZB40_Rg@<e#F_^9m1s>JnHSkRkb>mTjf(7Zz+7WdQk}m({-L&S{x?NSf2(A7UoGT= z^X+e7fGIPBd`KjLwo_j#jMauA$J*gacDr_f@-VmzM0b9>=p&+>@4)Dr7rq%;@?IET z=~sKix%jnox~y_nOiQBLh!Sk<8y&5LUGT&lIt$0tczJhSNwl7ExJlWP`gAw!cFqRl zv>vL}>9uXo>Oja+o3LXvi?4FQdbQP^_0fXLI0Bo+?s%~t677vul4v_*70~}}lMbaj z{;LVy3i=1Sm&AJ+gUUz-Tj7?SWEX+`o<CuOKNm)RNOz*_5E6XR-ynS=o)5&9`*C7M z)SkLQmFO9w&tt5iKqXX|KAvD`==JYA7XcsXXRpt@NWO?D+m9Eze*mx<9pmcB)U$l) z!n-a@c7QjU@{IOw--Yi&!W<599lxAGxE8)UQ0pcZO#LW<v=N$<t9w_?<ueq6^<ZPO z(D3_ls3vvOXh!Z6ISuFc(u*HaNP}iB37yKQ0)>ER{(VpX3D08%EB1o!Gmtn6p45hA zz40_7#}-bo!tIer|7j6Jk&%QId$gZvOOTmik}O(nXkO@KD}0HA#scDgXN;u#Q@wgN z81b>H18J}nhS(#yPvIdm(9M3^#*26}ZJuP@tcG9D^>j7jWTnOO_vKr$yvq002hb7v zh05ah4Ufq9QnRzAUnJ3(zj`c$bnJ2LO{`DZ_t-Y#7WW9U#&(Z`t((oF<3y%Evax?p zCpux@MU`T%+2cOaQs_JJY&Yf0H9M-+H|mSt4^U)(>6J^&0x8|SbiX&N-Xc`Qmp6>0 zm~H2Uc<7hyoqbUAw64)Lv(y>bIdTfE4+!6vULr~giNjDDZ_Tt<o>ae3$>_<0UZ2n; zK2axwg{IpTFqTx`PL-X^hyrWN;Tdm?Z-1VyG~Uy)v@Fd@D63A6ovFUsRDw;@J2j+a z#ov=1hzW{XH5y9ZpMbEP;R-k55>y~2gUv`|4r&vF<J`bmmbd{Nhjhi_!7vC24^^?w zL?qVU8q-b?r2K6EZuP3N`hI5QydRa7w!2HdM>>mG_2>)3unttO)*iDjAF*fyBl?&5 zGLP7;u3vBrCc~IA5O0xLGP-();IB`1j#GNRU)+4(a2QP=0eFxo=co>b`9)yU1}STD zC{7}+986;zv$kOf!(XNuTfg`<nB$@q^sL}}q6}g#pa!58JnkqmHAzP)RZ6ioR7H$# zGrJ!sxE>P4^nX#q+GRnXda#@~T0I}0ZzF2Q6!Df)U<95ngbkTn;NC2;<K#p|xhaZd zzCDNZrmK!>zIqRe6$j@|xi48!1GD`*m+Y#%w}|=D9pU}hgVc+EJv-?V$BYt2<<H|7 zKnD3Y8R~g(&W_hpZ{)Vh^)ZPz^(&g%v=X6hI%t_}GRfT1fI(*bL4QbLV2Hn~1kddy zIG71qEq9F-m78yyuC?_h_~!G#=LLT^w=q~pn8b|~q6YIh2-wC~jKH48%^4W~5oZ}O z*}Dg}o?glnHz%X=Vm}suQPje|l<gz|Q9Wbz@r5yn0?t=S5>0{OeKVZr*#qLb85YLz z4=WjO2V|c|TL{?<-T*qJCu0khXqX@*g11616Y~U>MnzCo7C~-qF1_!I*BiYIgT-<d zba%e_Mj?4m#GDl{6c?0?+RezcjnpSQ=^2@l^`KuAx<dU$zr%SYb>8)%b;ZFX_P=u1 zf1a|Fk2{&MS;;L$%7)z}Q(k{6^~aIJYHgZCqjxoCM<0yhb(ey0b`Q;QWA)~{A=iB( zM^#y62xPhtNWKFo+fVmS3*7{yBC+)>qrpsUbR%dQ%whYu32fNh_umNCGdT3ajo95! zb(;~&u}{a!iI9fPseoUBzx|s$Cj_l`ne%((X2pVW%Mu&HQ-GBlA=`FOs?N`3!Cv%; z-pGEGOseK&NO!?&#-Ecwqiic<xfi|k)T?=!4f~N%2kvVddLB9O_01cS9T9sq7o<Bs zGpDs~Dj`EWk*W<*N3T8vf>~16xUz;Y_Z47ou`*fBRZik<?VUXMfgzGsl|9EQ`aWS> z!XvOvMU2?gjp2+;v_k<Xn)*Vgl4ezVsG)Ki6<BvwehtA+2WQl}ddNYUGT7Ly7bjxM zEm<gy8**1>lSl&ST8uOvF8ev5XoXPpOZiX?35#{+0snZI@9oL420-ko;~TzcrngW- zpHYAGeo8}ogQiF>f!p(%ld<-w3`kCbh!`n>TWu~i%k_06)0(5=sNey&;Ts>(;Yv<q z=;>F!Y!f_CI-jGMwR*~5Bx9eF_-owg3qtvoB>h`(-k0Hs5w<i<=1Y^wh5b-_z&#&E zt)#i0?%7B*=BQ%;)_bb}k0O7|PuS=wCKdw4J%;9K`t&WAe>ah&sm_pL`Z~l8SJ!<M z>iB(}=JvCVb-wGp)R-JqgM}$KL;TQkbVOx!<T=Zy+QKI!`;{dr>Z*fY=`^)IFEl~l zU$2@-pw^|O6Bj8QA@m}PaAMNbioBetR{HuX3(sQaq@oJj+7Xw5rbW($4qxBpW@k7Y zwp5YHW$f37!^#X*=iF~5wnb7>u3rO77A?xY>%QT8x;?0u7;@EjQ1<)Z|6sQMdluFI z-fsh4Fxc7=r9VA{m>3l+*HVLdI443xrc9^>NNvGRd3)8s1Rdrwiio#RXF-Cmrzl6J z1H?3>oWtr^j+e@)I}DbS6aX!*Bojk!HUT3+^u}X}l^<Q^3`NE{!+u+m5&EW)znO&9 zSF9T`07No+S$RxNNcffI`_j}2oqULCXc6}P%6aIyXx6CN=MV_B&~XWfomKsT==|Qg z)=l<bOqu_LGH5B*Xym4pzxDJaI>$UJ?6mRO*(P)OxR)Q|+3-n&O6eMMhnL~{<7q0v zyoyUbd3hWngmd_Y_7V-G^6k%nXmeD4K3WB@bM5G6*3tl$$EcRh*h3k+mi)t%N#g$r z-XLH<WBFn0kPXKkdfryt%JUvcme<Y&83Z2&iG-m;IcyUZa(CE}a-g@;_EBtGmgV=j z$(Jc|STE%p<465PO#grP%}y042eROjB1ulk>J5&>bc>JuyTy}uJ8WgT+lX5175It^ z1X|lDMSmU1yhSyyUXTI;0vxi8YW~Tg1SWL*lNm4z4Rhpd?)5!V!z$)G?<45-7i`j- zM){vB3?;e#JrMe{ZP$RhH&dn-DKz8^&)}IjtwaV#%u=DdU~B@zTn>F|c!GJhnZ=In zy<+TQ9{d;c=O3`a+6j?-t%=Ol;T#eIHgmtdygNFmVpaMlEe%f};)S9>4~nC)2RkXm zLC_zJ$A6C<-rAiBP=U>p>NyK<Kpi@_k7^D=Fy3G8hyMUh`2|q>Rp6`raUrbZ{%T47 zr;Gt=Hlb>A-2%p64Cg-uV$j|Mwvv9l|M&Uu@9nSBke@-H;#eR5a^}2Aesy>g2z^4e zgZQ&M|I<%@N&N6vH<&ch`#S*<**Ae0JxIj=>W@E8v*}^oKAV_UoE%P<C{?gZc>Y6E zv?(N+WPIlvFY0nR6X8z}Tg#IEVOijNF|)lazIH0MW=j+N#-utv6-y@}e}{S}hW1qp zc*N)7laBjmr)&j5%t_wo^a0-#SCDqn#8u==X#JhJt~~EP8M;1`Z9x2a(?3l8XG8n# z7bjDhPon>MhyTF3J9XrK_|NP3hOqPh9ge5=HgY?XsdXZMJ@-lN=H5omWloP6@sFp5 z{?mqkc+=P5rdV?8zjLAGYbb7Qp03g_gKHf;r((N-^%K-pr6w<S%FMtF_AsQ`SHC?= zHtiL%<G<4o&0hiAXfO^c*Pa{A*#Mc%$o%WS0K`Zh4ZOw>Wi7qyPLQOZd=jQOn`m0E zSG1Y{gC2j^v8353b}2#g>$fjqg?{f+C!*#~L9NH0_S}@BT(+e5Mx6B%pc>e=lR56b z=GJ>Mx_5umy>Cku=er~jvQnmjB)n8(hJP=e?+;iofH}m#LR1n{$-Pv1Mq%lj3J&?- z^*-uXE696vkn%A7ChNeBt(O7WPb`YO18zF|o|mOs5@oyuWt;>(<pG)-_{j8*{klh` zvbpt+w9zHNX8Ewq`?6uTqbGYj$@Y3|GQO1%KEc~H@p4&<Wl$*6oc{hjY1v{0`#@|& z^)begV8&R3{@gEiwd8z+zqaO13$?$2@J6vRauGDcDzS+!kznxoH)0zbN>Y1rq@37I z4-srd<$+g&cgJk+yeikXX}xdiwP#R^w98>`rq6|@`i@W5k1Xj43$_M>&Mi)#SH?Ux zK|h0#**KBa<UmB+L^4!!1>cDUe15<mX#)|~_X|z#ImL&DK{--$_V>Ls>7gwNbXr!@ zf9;9BrjYlMM6*o-*48hSCdmvX$p(F-g~`?r?qCL*!+>ip`WhuFCky!@pyYabU(ouY zmd)gxwN<EiSM-`<p^s99u)eK2cdMnU2rSJUlbUg%<i@u1_0b1mZHZsL+o5KPEvZf7 zTyMK;$#;%H(EZ-fS*6Z<q?^w(Wi{9V3q~@oE(E<|q=pwU^o=T|!D|-V1>7HYx1W^L zaZ1@r0-`V4W0U;KcJmaP3p?U7gdm?L3eB^({<fMiPZDh_FpMb<p5&7LFs>%i1Fs|z zVk8D)qJCIJI9^t7(9l;oZmsgoc-SNc&XxxBcV2#6{7mGpE@k}@7G(*g>WDlLI#D>G z8|Ew~*klEJPi(h**x{*2^;Gzh<Rn>T>3yYD+fO2EM*0pd8Ko}`RDI_o>z_GXyBG0H z*Yql&Wa-#L9Rr5exAl!!@NV#tD;YR+Fg;K*DEH+ZgE4$wop1t4-3(mx(H|sUp2S-8 z#gCny+V7Av2a)~8me4jWo5KhT8_+zZf=aZyvxkuHWsXyr^KFqb`XVnMtmC3sOG9m( zDY3)uB>#FaU)}!U_D3il`ObM(3zrS2Ly2n4SQyoN!8kr%H7Jy--vVj1$P(2<aZ(>h z^)JU@-Oh7>Zo8SE80*r^Zq`|cr=Z=ZBvP)a53curX?b6{N1ZPGd>m7^Z5l3J{&b4e z7(s{Op+Bl*UGEc#5pd_ML2m1WsfeUwBCJ0Ow}MP8Vzc{-%ur4`+Wr7RIds1Uutcdl zU=NhGg0O80y@i?U34C`=6G2GL3LA!`S2w6cA(m@v#&<BU^-xGyC{F*E>(rCh$1Sb5 zwLouBO4>S3IaBYTzK}@0eH$pT=_2t#bw)v=p5`b&EVwIZ(tOt#r@GRO5=bm{+4YcZ z`K`F>MurN54SoPg266=%@atyKNzsHO0RGAOJ6oZ3Xm?Z%9HTIItpyvsZ4>jXv_uT# zIkJ+EA#bnrl3IvDVXQ2O^X@wlukHu5mWV~vU~qhBfjO&_B6GQhvl8$;+a(3Hk*$i` zX+I_PE%EA7F5S{YeOJ(0zZuDraM$-Y1;$tWe~c0ESH=t!L%obcmn)UQGHnTRW)e+o z#|anW7ro&|9mVakBAL?sMRc3N&Q(17J@ppGRr5U(Lq!Je&)%>+FWkS>S`Hdy4kp`h z5eGC<EVf*X>HJmu*5`iHoXgm$Jbh`#N79~z8ERnkfSJCoNiZ|yl|-icwWr3_MImzP zytA2S7`(q3SrEKuoL2hqr7y8B9s@f%H711dw0tJAsG%}!V#Ly{@9A%LuUA&cd!NCr z$3qvh;ojS$Y<C|Pv{Slkki-{0>a&HERB@AVtU^l4Q~EFRdSpgg$NBU=E1Z$%YAfe9 z|A)J;3XW@8wzOqgV98=;W@ct)28+pJwwNte3oK@4v|8LkTg=SN%*-13y}9S!bH<+e znTUy)zug@fwQFZ(RVA#<%zPNlGiMI1ox-@evG5g-4v^jHa+F%1_o?KTxo8wp0&nFc zlpmPvq}C%h+4d_Wp~(D>WJY9&VfVf*uz+xYxG6XZBq}GK+<^7nDvUs_qU=$w-4;<@ z1m^;Eq)Lm!rw=DUue9@zstPAk!#h3Fhq}0(M5G^a1a%iz_PeLsiSyHv9<}ip1^Y#L z^BV3EbEPNy&21zIUaosoW}o_T^zk%V&xB41!)KkK#l?cNM#K>*Zl>RtAK2$tXRQcm z!oqXVFh`AZH^A{xx{cP9+iRQ1@8Z+zR~T`{%<6RKHUF@|l$*2<3f_(D0y}d!Qr>+Y zBFi#%>y)(u^kys|)*QYOTk}=(kN|Wi>6PQqEMN~Bdz{(1_?t@F<}FQUhZ!^(*GUcj zr(XB(XFs2}Z+{t=*>GE4-N9RuG2P^Hbl=|G<5#?Nkc3kbS{aT!FqSA=mixy)$gM_m zUR*3EXK$6CEce%cY<jKd&_yb3EnX9w7LQrFQxFNXk^4SpC20lxG8eSB#&EA8ho<=l z`{l&~j1IXIr7KG8dV;4b4N*12fNDC4TFDREumS_e`fnQ3e`vUi@!;B2A;p+#@#HeC zlcmI5>_yh+7|QaoDsL}U2N~3DV)F=`)msZWD1LvRHk3kdat9?H9mcJ0QNc54E%1>4 zFA>}S$sNB(q~054vJ37vb$_O5ClL@VV=%9#|NZL-{D{b333}<@LjGMX{<jvSko2ml zp^{l&|6#k3C~W>BxFcPC^Y8fN-?hCDv9BeI+Vh55|3na$_N%5*8#vB?aPn^)7BE0M zr5GI6UYg05PSm!Jx&Ad0!3cPBOH1~|-ueGY#eTJ(PX4Eg{cq9w557_;fh{s==H1S5 z{y}LIF!*JP%y!P<|A`>_mofAI!3d|lwkMhce4{cAAD_P~D|fqhOL0!IPdm;Xlyiu5 zYqO_bP=xjizNN4k?wc6fW6d-Yofl3TZ_7x(mehFK)6V0&rGc2HHz;iOt$s~-f1Hpt z<1@yYpE?~@HSM!Ux7%uz+jsxb=8n*BJZq;+N=lvME`W7i!5?sb)R?8Vx2Zic!TDYL zBcpBn_=f%dt!C%JxMGR1rV4ZnD1Pu}f#22$xIl$FLqr@MSMkA1q1?vFb-uhu=C{&K z?DvZGSUukwm^T*?c!R}arcdK=@6JvMCI^|a(jM2l!f4weO3kcE5wx8SX`o+fp?2ru z5R;K3?9f<XIBT7d<LFP<VGmjoqZ^PB=V7utE`U4w#w|!=ao|7`**`8eTG^xQ3hvwh zn(4X(P66*b5y58~#K9R|Hk~s#&-xE0zgy`<R0}SR()AAWd_U<ZbL0L-(mjQ(s}oUH zMj+$kCXfNHSm?Fo1jbY%+*aO+&%VS0^RbB^U=VRZJ+tk`VD&qghz|_dYxN}!pkd(R zj%oI5`6iUkDjilup)!$gab}OUTT*2U*U00Z8SRx~-UVdl^+?}{N<wY-#tV3k2)58{ z7Nz`x<t1+uE*TS1Cl10w?d|)$q~<l_Bx&JYIOP-Tpeau&HQ~C-0r@nd{Wg-K_;Hjv z<5n|v#y&huKfdhE5aWi9Pj@us&9X)j>vSu2FNh=iUV_|>`qg4&h6|0&mAw|=mP)TW z!wuX;SxFazQbo7S<)_5X#=}x$QmP6!131=p{g}3_I4qjS?TP6Kxus3IfAn`0*4WkI zw5Wv`C)+j-4$gE^p{2s52_@)R)|BcBCTY}jwrt3g5WN|Zb47VuB5|lE?MoJv4-F&# zj@?8@nv!xoFTYKqn=>xv=4;{SX?wly#_0>y^HhtZubmAC4t@RotZH==(KHwGj5?0^ zJeQXwJS4ha)qFbe4f&F|9}L0#e&_YTL82%>p5f(<PD$CXjBEk3-1?y#;bV~@ue!*7 zX-?w(gy?K__pUDjPwWh)&1iePgGgsr8BJ<qtT01Fc+4@Uj;_5PvLeOI*ey{19sm!x zUiS+Ja5GpATg1|A>SUAZV$EsBGV_F)taT2bFnBG=DldG7hwJ(AS+(ZkcH`jmEGU*J zYF9ZyPp6rqfS#&^dc%sxvh@c$qpHHe^m~!dZ;n|-JNNZI476&8<*vtnPQKJSiK)D3 zdg&;zOfJYl`su|`8`V;tQ9u<5FVb+g`NEcc(*SLjB-;V#x_5&)-En&2V2R8X&ouxU zy6Z+yH%rZUBgEdbPu)u);hkum7NMLIWSo@KH&r=7^YJ5CW4%rVO4q%S+1b1CO(CI| zwO07vg6}G1X1CFhr|eaYKMmq&_=z(j40T-WB24P(_PEh5qbJz>dz6Wid>p;7dbZBh zF~*k4m!>HWTC0fSsSe_`);V20uHcJ^PZy}IoJVjg;R^*f;t~@DqqzJzI_xG*NzHn? zjLmv+3ZFgcF&?)5*Rbs`XOlBnp?qeyX{J}u>Z*Q?*%+9Cfx#R#XD_1^I9%8XPgAc^ z;L_MPUb`1wRiM#@tSmUSE)Wl|v5gC>9#-t^66W@~+C-y~5|$Yvv0V(Y!(y2qM}ArP zy}A7kg4@!{X4^)Aw1ECjHU^az{f2y4R`vB8wsaeSx6{?I)LzxcikDM+GtAY<Sj*N9 z_*C^Q@1>nF1o5-@5dJNp#?{be+EqjsSePB#c64dDwV|GrcW^xuZ_M|)R-G@vY@`?Z zbiS?;;~A3sJiQVBvURq*D=yk8-TmU766};%o#h!}=T=V^gxQse9ZimSqRyOH!}N_} zL5O#9dK6YMq`8H(HQs1#`!b~qsznzsivNn^t2DXc)6B|h59z|$wWCh$^CA<Hg1BpO zc?mFBeQjeX6}WD!16zg~I1(tH57EL{r_K-)uvJ!@3anFv!b>Rswk3>}E#C!aHc^B% z{?Tx)+enA;kYC6C@}(|k+dua#>HM<#3<1Uh53HWKhvA?j(6wmb$!vi}0Y6i^&V%hI zqtzU)pYv0^c$$^gqNAYpJFVRDHGVrP;y3hi?kK2ROZI2RC{pj^#WA_P(sh|v);uT1 znO75{zkb8(82hgH9`w9ocot2pAhdEobh%f<iT7hU>0xY)pnvN;rxNQo*Y9H~G*0xx zf&10|TC!=z7>?b1opq`u-phTVjC*gL&}sq{-}$=oJL^wjMW~pO%F*nK-SpB6hY)e1 zzOL?~Pe4TONWHJ7pif2W^<{4z8m+p@0B?ghjGwQtb5r6-_Eq%Vqcu7!T#h1%f{L^5 z`HEigYPUkc{)YUbBE=%1M^_3_XekZbniY%?UuXcA1BIk|sxB;es!4=T(?28-$33MJ z;*LBeIVgc`&6X?l85&6910DD`S0^;QSW0tY6r{mz)*66H7^eEHdnlzV2bl~z$0lES z{(B5t?`zKGM+Z7xy>%IT*>>Q>dcm&Nd)*oNjmbXCA|)LS(##IdE)9`Fnoz;%nIEN| z)i!3tR#AA7NO(Og^d(fP0K#))jtaNe@N7_^&bZhf;wXmo*Dy<y9h(FpxA@XJwEhI~ z{6lw?SlS>q!=#dR#MzG%!CyrEV}P?XhMU-qeC_iaiTR!QI-l-^U7X<!RYcF+7YKE& zBj)P`ihZlr9nt(VeU^d-s#>btpAg~{8LF@10eAfmx$4AQh`WA`SJVPs6&|}k3sNZ5 zOVqwO>(FT@(pLZ8Th583kYSM81^T@D!w-YqCXFm;Yqq!G>i}=3)#1ujArSo~hCmZl zK};e|_l35VAMrahn#rRSk?4C4`rHDA4_+B~Uk%%Aer$i_4=qxGEq{&k@RROSL>HE2 zE^kPrT5FzBC?TbC?jeaNG8VA-yXFp~K9#+htZev@-{1c%Q=i&E==}Y0+l*&ij?&tM zI;7#VoCSrT)4M@Tq|yQ7`6lRYc*(AD)`ioJ@rhn4HMpl>J}My{xg3p@Q>Y(~8}~VJ z`^uzq4p1CuU2$Yv39dK*AKqeB^){*aqU=ydq$AkgIF8RTh1v1y@T}n0JvrWvJ8R{Z zcG^`nDudX5TdIMsa|s-Jbq1gNXIm8WjTyD0wQDcN;?g>o&Wp!zIx=nO(6_<Vo@!8D z7AlcG?uUz(J6o@L=<_-U<gZ6B1dE2y1V^r$Z2LdE(wXym&pN-+OXGZVQbmZt{Ad8a z>*DvR>(+rwFK}@EDLSKqogmUEDDS3*S%=(+P4BmUVj1vATy6+@disW@Cd&nqc+**b zc)p_aRH~()KihWr7PGcAfQV69@!}#W(HL^Am^&<tWflMfjD^|~HR~O8$I7|cEaQ)t zSZ6Hm`8^vx)M$oBbwg$o7I6i^sMMN$8qyxMNBs*-9BrnJlw!%#v$PQCd4iQ|Qk{AY zgha{s{SBP>QTrWrdL~4VHMd->71mf$TN<7gHSInb*6&Hu+N<13&j$8ivbUJiWt?#F zjNLl)nn%lonh$VS57{IWF+S_o;{UvS-nPe}&QObWrZ0CB#k|-QOuvt-=w!F$;X8Ai zWJ1HzBW8asF5@pumSyxYKSSroj$;Dm{GO8}2A+++mEnzQh0%(^5%2_{Ktlbhg}(bz zT7^b^{Tvk2SbAD9C%wt<RkBz@t{an?H_^6n5g!aBs^QdaVE51)-Yz8(TsSah7(xG? zX6mPn#NqSI!)CKs1g+m~9RsqmsGn~$9mrDxbYARh%ivV#gmm_mw;2}j44veBNaJs) z#guc8>D-a}na|s%KVi-84;A;dlGg(PGnU}2K`L8HwDyTn)B`CWl>J1!#hb_71kEj{ zhfXTmS+mtfj7d6{6X{AgWgbaY<#nEjx?m@q;hf}Z!0*k403Wqa_<4lBX#+T;PB{tx zCAPJ0-PbBqM2f~)O0OFdoPZb0;|3?8kUH$-Q(st-5|=bYLQWL@=Mn9U3#n{TVf?&| zCsa!lY~w0-rb4U0MUUH<pE>M{O!7u&)6C;8jXY)gq;Q`bvPCZhZYQm2bd9(?`E7~m z`Z=bQu~Ba@CP<8T@;fbsrNFL3>od*~CSpRgr33&G;Ued>(kncwDFI%VYU?8#iVbA) zPBP)#!7B+*@&WY@r>q=3thO^KHZ$!&bsjM8I{0asf}SEqaYDrms!vo|K~@!UHd7Pi zuLshEC#$2JPjNjN@^9{rZ@!LT=H}+3k|8Cp^1f~Unk#eVGuG13*)D{WK%=Xl;xqLl z0b{m_naa$s&LUe&*vHf7%g1FND!ETYaZ}U>6;ZUL?L*1DFC)vzZ&Xybd>#S5Je}{g z{ZK^Tzn4W+mKiD^Y#Fsu(kd`X>96WCznfths*#nQU@|E^$~QN$u&4kP7K5_Ce)zB* z#GOSz0Dt!~+;Y<T#ES>e@|Is~I$5sv9tPc{U$nG24u2@q0*+%{_v_}4D(#wRjLgEJ zsFaSy;smpm0P>mnC(4E<?-kVtYyz!oSd7jRqoG}0<{_FH4gApr6>`COxElSP4>|eo zt)z!ym-y{F#0mE%Y#BBdLX!x0{P)HCu(Um#@T<N(g`x5rB|K89leI6eYTeJvUQM>B zUh6$D)nj;)+t4J1<D0Zpu%aP1xmV?y%z&eMP(E6+iaiG<yXw+2efokyhK)cG&4?Ej z$rB1+`1Cc3Y(JJvm3MKBQF8k$S+>WM$|Dhu4`SxC6LG%Pyp-iREsBoHQvPtqK(Rpk zC=dg)a9x@14W~F$rC|@<Q|V*rdNbc0*@B8{Xp{fTtAX8<RSK+p+LIal6i4Xe&hYYS ztYPQV()ti7E&Z0N_Mwii%*~gbul}%^&J)^8ZkwFH*?$eN&;h~0xvJD&+O405u(aK_ z;#G52gHw9|#R^;Lz|y>x%xASb2`bESiKdvJOOVLaSx6uxvvCw^NPg0Oq23Nw$S9k( zq%QYjjf9n$_oqwRGaGQ<zI-(3T<??iUKGhqKTnfqmg+>3<#gFn-l_L_fgnRASsyc~ zax{@3*>5mZSoU;`xe+?UM9_~BebMoc3t>&1(<?A+loW0vTQ{%^nGlT##+IXU2Z~Y0 zQ6~NzhMi&s-cwW3p_)&~rV(LzQ%%s1%*rOJ3f3@S(XsD=q)XGzE$rRzb|!!ND+RE4 zl1wmbeY~d@E5&8r`>oHVB;6M~IcI*Np@s;Ba4=mz|HGQ3$a%T%TcTsv?juGU`n$8Z zik^nfPyM2Hj$tGk;M{Gz*r(T~*rQ@1$qS`K{SwQ#)3}aO)T90!Ma^!_+Y!Ni=IP82 zmozzP!b|;=1Gz-~N1B(sOeR<dyu1~a@Ptws9)$82n{?$4{RbGJi+WF*bT-Tb<0sZF zumK#0{lnyy-`+zd0$KAkom8p8B^n9o$S}Zuw171_PB2mKf{pySWTrr+0w7!?q;M(G zvyMZ)Y0z)RSUo3QoipA$H))U2BLkPQ7GKS;OO!Ow{JqbB+K&pxP!)Juwfz<H-#)N^ z_{Quqpe8xw_ZTpLBV7OOUuvL$Qb1^;DDg}Ft-tB@PY%6TuUR_%xJ>Y$BKA210)mG* zMG&9A{)xg9@iofYD#48Vhk5t`ew6|T<X|O1|B2#4^flIdM>O>1w_@`8C*6nF2s&Mu zg+AGzD2CBrBhEVjmI=cD@TmRgXaC<}2EuEeI$c;e1M;7jg%4^}=+IN)GuEF-&_BLT z6<t=9f%s1pJN~a6yAVt^|C{^qKirbqEU)XP!=m~#{7)2V@UI*j=G;Q&h`Zws8+PLA zBDY`t(dU>R7JLh1Z+7BFksCZo|1_bxqweXz4~N6;7@fl*e<Q$}_`K=&jzdj*D4MC! zf>hy)9lHaJbSbNxnAqRrVRmi^Pz^j`ds>u^%rl1j>;4*E>of|~2<qYi+43iFA(kp5 z^-znqRB~E?knb0jXQ1s}l*4Xe)^x7w9b(vKAfnn)1F};~S8{A*vSXdmy<qk2xwy75 z?xkc{&lC`GS;(zLEv*$6F*Eal$$hAk)Dy^cdz3tl+HsCX_yly;m2|b;2)y+=`kqvE z3_dRRKv-r&&?qXNWqU#gm00KD+Ck)*p!CjV*_)OK>?oE}q`xM=H~b`E?X2RcX#T|S zG2-@OV6Wk9`6tHMxTR(Ni1GU<z*)zS=UcCHuE$bg>nH<wTaB-_btt(bzOB0`<_@PW zu17>N{Not#um`(#8G0q5zHZbuN#DZW*0cXuSr;y)R1y>Z2aiAqS%FFU1%Qcd!ov+3 zo^Nhw-lF-voH}2D*l?>`SDdPrFmIocS|M<P`&9ws*1K6^0-6Y47j<jW246nTwACSY zOni9D&s;T>pA_eevSfOAjn*G*xl<g@x4AifspRA>D40H06zMOu^);uNE#v$u%8c;> zDuZt?Sewy&kXO-{alpmMDH5u4-GMoBEjfIHBd<mUQav1((hURnT-2d?khtFpIcQp` zOGNL`7tGpa!C5qs1*<+ubAgp;$i&ABABs;SToF66SVoM|bl(fAhjWs;sk*Z$eyJQN z_(4PQcWp+%38}~#1}K(Ic-xi%++C=G<`UhtJz_W6F*M&cXug-`irhw1F?}wjtPmQ^ zr0tmVb?<evXDF+KAO%r;P+1WX_=<f|fy9Zgp<F>Jn2P`s7v+il*;ec<oCG$-IZ{Ef z;|sg{<U22mfOXh?QY=g)bu0OrrOaeOIVy|CL%!y)b(2|LDKSik`9gkaZEThuNI|7y zq99j*7vEdMz4N8Ufh)g!OFkfGR3ZTDZh{#2WO)k>C1{pJujUNVp-<_O#84v|7=zra zN)<6FY(~68See3<kCBi=A>M#`PsqbGj-;_ZsF+g_0OJT(=s_9diIyG%vX4k1R*_cj za+E>V7Q$u)b{f494m!w%jVeyC4kPseN<7TDL7>8XURR2ypWVp(xy^4gF03<i4JEYB z_xsH^wpp5!Av(Kjx`&&-S9M=*74u|8=9inbC)C&;s5ZSbf9Lh?h<flirBK-4PhHIB z*l*NE!Yeyhhnsa@PoyfXL&-Lufi>*QP+@e2<bIyq-O-thn}jL|7Gqoc(MXJo3^uAZ zo=u>^e9)>eoUP>8tB0r2t-GP=WHDWNtcm+%JMDQ9dNh;zr`%0V?GVVk+WDxlNaDD| zSB$}Ygmxq2Y70buGgX{B?>K@o-5DlVXf&C<<<0sq;B~ycT)oXGbKH_5XFu!678&u_ z=cTfsq&aO`7hO=3s(c=f;5TG^6Mp&_o?zF?N3P07b2LnH)7Zf*t5coOQ~#j-KAZUO z$8`}NSVmsBRM+$Ns0hz=hmC+)s3^42zO82zo2%oL+@tkyfmD~zF<5A&qBxD<c{&Y9 z1eQ5;w04!BnSr4V@q^x>Ew(SEWoe=dait}?jBPb!M=M`RsH_;C?NLgI<h8%ihL9o_ zpC|Eq_{{~eg$=IypWMXUnw~iAU!&9w^$W1dI2&(l@I!#ANm#})Z%OPRvdM&DmCpdT z#+c~nh4cY?7s^a67xHy`>7rWpPZz}!bp<+96C2(l?GOv@sx#&rG9HYZ;0Zph7+&Mn zCT7TE7}EI1kTcLu?w9~(_0{btc*DA9)rXrx`#ubsiBWI8XXH<V{3cCE`t9ApkU7f4 zUcOC^^YCU(%oy;g7iW<nAt|M}VY2Tm*Za)Ht*(@s-oib%v}Hh2mS)CX#Y-?}@R@16 zE$@k0NG05QSaKZ<f%Mbh_2#IU2JaN2d2xrk&ztMdM~RM*U-p$VXjhS4bEi`+$eeCx z-XjXqOrv_hqHLn8Nf@!+NlP|s>bIW)^|^fPPMR%|fxn#YCe3}ziWtoFNR<gUoNl*y zK6z@9xQmefoZoz{ILwRSp}=ALQ$RYEW&cHTbAz?%!Q%YriGi#YNa6;??_CMo=W$ex z6jFn3Bb4S<UE6ke0IX7qsbkBxrxW|W5Q507wGcWa7mZS|;q%?P(o)Meqvwt1UDW%A zYE9Vw=Mq44f654na=C5%&OqK;c})o1$jjTc0qJP68`HFwge`|CsuR%db)eg=xD43% zNeHsyXKLNo{I86<I?FSpd5l2$tBDNrHu_B3o1`NX(bUg~8q9U|%b)Xg^j;R7c^GQ= zGSVGkb5e6`4T-jE7j`ZjPfklR5%_wJ@e(+!u87^dyzyEi%K%u(g;pbP5T)27@pD$R z*7@ZN4xKZdwWal1i7MbKb@*bZ(Ani=Me(lh2p67gZ=ubycn5qkuW@ZS5JO*Xaeti3 zT*;IXS_{o4xci7bT&C6V=q-maZ2()kFW$S_njv*xhfF&-*c?0NetRVO`);6!@@-*K z%Fz2h!yYPN5Mp3kE+ziR4YPW4sd6NbTk6<Psc6;L^3>ez^i2O+b5|O35)d!m&JC#C z)34A$`#N^Sg-A5m!|3u|Hjm?R;H`Dml~ra?(p^gk9g({+Kl7VFd1qw80_fvC3>)-d zdsncPXYo*j=Ohmg7Zko3NK3An&VY~HXPwQwDTV7bvMCtXL%qio9$TMru;v7~UzU59 zRgpSWNMsG;NF32ZzYlee*U-@F_V|2}X6@J&eKjJ2?`_hkml@=dJAyhziFZJPy{)`L z1xZ}pRgUu`ufr9TwW(2fUz4!=`dlN5v{uiL<(|nxT&7?YK>UEe6dG=b)R=EVy${7u zORG~r?zrz_L2dQ_b|Jum$B0gNHYD21)?PaD2xetDtvwKHh?bcNkbBJ=BdHY}j%}$l z$_mf8h}sz1dZ<w_geR*2m(MKnuQ_{1vOMidwF-Ohl^zP;#YFlbDS5C=%^>smC9_KG zqP(-?l#B58vl-;3gwD=#eg*oP?S;#yig*1GvY`gCw(>Dm@ISVf31=xyUab82(kr#S ziTJ{i84n3HXJi9MFcU-aC1vfLs!Ku)s63YE8yfLwbDS`l>I&D^ic9Tw<Ek_qcBFvh z)Y5E<y){pL?b~xw80Ta0GkpDRke%Za<4p5;31yQnuNfRlwhr)-mj3lJCJk2y5%VnF z>zA)9z^2)>13~bxJe@Sg`e95JwWB6L6o++2<Xk7TLAdoMwPCOWMH10#S;bI8_UKOi zR)(lRBqN@uqO+j=Uc!trCsYQ=lEK409o7}o&}ZKL88vV=jjmj+Og{Ihi(#v84^Op_ zg?uOVQ-8w+Icd;TcelNl;;jt6@sjuZf}8Mh3@X1@38{CQJlPZ8_g+ejKva%f%xi*j zvk6CFw31MwHs&8iop^Nx{BV@7sb@f5K3oKwbNG%LX&VJ^A^jvH2?WM8Ka&`0Xt`w@ z@~8(T!!THgg=M^Tduv~f)FI+26YHVebvA>PI%>Q*ah(iER;~g{h_w0BFNRm`G@3UX zSP-cF=mvcB$ul)Ex?Sed@Um5>RJTQx81NB0m@bk#8YzYg-x)AE_Z&;&PYxLo9ync1 ztJQE<p|Kp9^RglSLHrLB<OIA#6hS$`0NU_vg1Sl$w86%XRp+&pmq1~Rde43uQg>Wt zD=oHgASg*Y*3TcvNvY8Z*pNtL!1o>*=?t%F`Gj`hWCJMdbl={~jCi{aO`}EqRnQE) z0*-p3V0vv5b2BSL24wCRu<Vr|ZUY|fuf-rm!=fv794L6kgz*the7qN%k$?R)=OMB9 zBiv0MC3P6W<sJ@c=Cj#2C(hHB*_B{wS9C&g|0D7oB&dEy+y^o4-m68ev;X9D8fU<F zLfVt9r4(U6txqoDZFZ8gc#O<dS@7xY@~wAKOW-J-XRDyd*WGDXEB)p;AYKajRZq+^ z6Vu3Ww=+A}z*=n+Yw0&qczEGLisY}Dn8AGSntJtH5G=j<$T&*@_{YbThAO-PVx((6 z@BsrLutDkS6;)>N*yH1htzyZY3|BdbMF!Sxn7(vl2E0(V7VZt&;*O7y7r0W5#)tZI zEP@4kqB?<)T-1V<5T8TzvFVg~gtc`2(Tz+_-D`eu+1bXvR)zQI1c(}7?04w~YKgjz zk+SiAn%c+5pB1Kw&B;GB*yT8tDnA;ORNp%F%M{h<=OVp}JI#&;?#3?8sDh8hmX5Z) zZZZqYcX-DUn6!S-ln)4JC;0YD+!vxabyKYwvzoBa+H7IB`sqiu?QA?`%5T!0ux^s6 z+q_hc@t}Sx0nI)nQrZ^u%3&;tZ*7xQsd!W~3~{@QmByYXs1v$1Sm;e{syx00x{`wu zLMp^h;Q<Xt2u+PQUG7H_(JeJQnZ<H6VoP}iR|)#cYIO5t#XDV+R+D(CO$?+<@H2sZ zyu@eL<HJW?&4*YGwZe||cg_{0!8PT2!@Coe2LZXFkE|HWxwYDqwaZ4xVtY4VV)MLR z^)e)-C$6;?TKd~p;c(ABRasRcOeq~R^RHxjAhA(Af8t@Q!fxNvWoEUA5<Hr0DR`!l zV+;>zw>)ps=TU#2J}iS$V{+1@!_}=**A<vV008|${%c$9`tM`z{X{+LFm{{fn<Nxt z^ZoYat5%3;u|^acDnU~xp9}#TA2Y0ay%pt_1=#Ircpbd8&tU~zLWqfJp+qts78-yX z-#nLhKu?bh?4GlE<mjT)=pXxO1iXYMq?ok>cA&D9oX#GhJ#H!~W&Se<X*eZ+e=!5q zVb{jjF~&QwxRz1Kg<!THxK(n)A>Y+EpoembSdWo_B)l6gX3nZTb(ZbZ*fU+y*TT^} zpXw#TUT$27hz7gHzd$aKS|S1*r*@kmAbNteMv4Uu01}M!-~3XrzSM;z3f6}3T8>|G zoEwjM)E=w1sWs*&>HLtwod&ew!RUb$X_|6Q)yycnzt6+9-SBGIfbtY*4($7~s-nv* zKf9Fc601ffm<Nak&Qcs`wva3L9O}d-7-p0DzX;zEF>rzwt=5Ppk}49s-o7?wfs=G+ zw3oF$XrzRXj_6{NCTriqxMqo|(@3N|pjis_3c|8ui~U(|FBM5-rAK3GNw8@1PS7j* zx)F|>B4uA<NdxjYzY1o9i0Awf1#q<y^@%&=6?QpA(PNKzbvWGn#lW(W@07XANGJxH zKNJI-1}y=8U~=L}eCcED(Q74^QLWeV1=@a_9^%=DeRPKLYFd{A+K9njDWj4osSg7D zKqjPvTJnefOpQmLnfLe*aiNPl{ukL^Dvx;I?h~DUT3yjke?#2o8!vbLYJ4+5sc6dR z{Oqm+o&kaY!R3U&-(CNd=0)^owfE@a0^Y}v`!C%abj{zp{8%ZVt*gNWW~AFm092=J z6b2+@Y3`g%@d&0}UZOpA0~H$)_<3a?aR<8>)@?<4s6Bf_OB31NE$pIw!>1qXe5Pb_ z(s3!Q@sJ6%H$VgEwiULC<0GydruT2i!kIrjFDjp*48XO%q%sVBx<<3y;?!@Uzl^&N z)A)SSj=gZ)qCK}OQaxIut|Q!0v*YW9aRA)=02|GAXCqe7wSSD@jDr}hoq^0YAaJz^ z<(TBXKfpYnA_ju=BYHFM##GzMOO#buYC6H$GIBJ&N%(jB)mI!WG8ZpunMcpH(G>eA zCk4se7WH#LW!bK==hUMoP%$=MKcH5(83PWi!(?90$Ma5#y_xh~$;^zg1;}DT{G$k< zlWGqN?mH(z4;k!9S(I~JZ`^(VRmwN#uCdtNn>&Z_)N*-5C)$`&g1m>b85*mv>)+dh z4f|Nu54Pr487~ir8`v6)HE;qO#Z>p0nXc$tx$(UA<u!6A)4asLnUEuJ953%QhPcZV zrH5@2!ixqvBf6h%*nL71x*>VE?PI;ED0rAisP^(t7hBZzDg{{h9yTk~(;jtLq~|C1 z*bC7}vMo)aa&B-pqJJIC*w+~Gb@6^Si%ko!GX0`xVSlgK*~W>yVmz<a5wC@Cg2z*1 zZ<$r8W*E<gQtV?{5Xv%PQm>i{BLb6*XN3d<QqaWEu~yOlV6*CG^j=FKnAr9xSLw#L zYrR`SB(v(XCrnQfWOvrC-*pnwLGYR74Z$HOc~<u)nMLRJ9W%ze<4DA4u>#ToX8kDa zDEK~<<+7%V&g%thAL7X`8i3bi-g&sZGURM^@O3%GBL;~AU~8)Zx>6m*89TVTILLe= zp~|Dtr^YIPY<&tn)`@UYC4{jmGIzx%VPVOUaJ&E+=e+jmT7!J3AE@*2xX9fAMmG2J z1~^e`9MKA%%r*xzFzMJ+JgjX$cg$O4>RX1pOOXr;s!T7AQp^<%KrdC%*^Oo0ta@i$ z(nz1+7r9l_Rm`6oak9lr{?VsS9w-+pWl@mM0e=IcHNg7^s=9$t!9-9-)<@-d<6~p5 zN$rQ&qZ+QhIO?myw5w*EHnJvJs+k@nwFif>rwQ0GsT;<Cl%UWg%866Fv=%hmzPKN= zN!wNXF)gLvVa)0KQ6zw3ZY}`p#^^O_iW0&&**SLXBl0;^!yX&V!(^F+pQf!eC`Z9R zLvd?a^FHWd1ZKX>PnhdlbZSh!PICecfX*ldQ7A~p8n{;$lHe?Su!yrjGy{e7R-~Ny zs>!LGINqNpS;?$6hl#aamWbrpS~aNA*r~0s_-%`ZOh6+WXcWriI+}XoA`LPpyQ(f# zCbfU|q(OIf`RP-}CzkPY-?GrOiFcYEl%fUY{VapuPxgZYG?Lj0oPScgDA;CF;@@H} zA83dat8<wv(~;XEP-k1X$XPFPJ_miQ{RqXxdVtdeK&7hI;c(hje&Arg5l~R^2yc|0 zjvX&pMC()OvOvL%Fq}UA8U-0<Mr!%0TvxwFs-^<VLc|cw5EtOAH>`bLTeM|lOY9bv zDO59j_AjGND3XtmL6GJ(KIuu?5;dDB13t)7%COe%^w&%hi_vW|qiZ_X1HFGDw2N;j z*!IaEGa|Mh^s*=e)07fQfY!L@Q*3o09nTE70iR>s<(%n}*!KsSSmCW_)?F4-1)>9v z1w?c#DxJ2PJ!k4(lzhBJ9o2oTbvZ3kXz@eHlPN}3&-!!JKJ8f=C-i8ZZeocqX)Ncj z?GbL~RE3@e2jfjWDzDROZ#n62K+*W#|40{}MOJWBD%qTzH|U?!3NFSaWq-c<L?Wk6 zBc{s64BPQ(@U-+%2S?5sK8*t&8=GBlMFZ%}7;hLXLn$?U)lt3_!RqmqQGs@lk5(^r z__D*vKB25M9TI&*--`Ltnry3&pG0|ZvPNiWyth(iEJ(E0==3B6mSjXP@YYuj0}tO9 zMN>;Q|Kfaf8f~mpC6$Yl-3z_onWQ~A1m2|TqjCMRj`0zWmx7Mw=ir@@?t!^lh63RR z*so9a6uihcWifu{X2@Ri3dR8%^*CMS%xXku*UnJ!POi^4ce;|pMIil4ken5Z@l8`S za^l94pW%Vs7j~PJTzH-<&p;#n7+78Pqxi15r?Wxd&%LkyL+f}Bgr&u!3U68Wn)b@O z;$3!w1dVSs!fC}Asr8$^DASfA#;}ma^JbgeOt>z_S^?XiSBt;PODGX4saq_Oc7zsb zK%25_(~^H-_{4$+$8_W(Hcxl?k@n{!zOr2#68({odkLWq*p*+V8++?54epSK7%?3S z2I%2B>~<&EyS7EEW6tDfhXJRxwllxie#DZ*E@s6M6ijtlY&{3Ep5%ON?bV^x*!}Pz zni%fhnx{E_JP49!#>f8Nkmm-#f4q-Odc$`5(9J_@WI#lc4l|SIf-m!Al2P|LNtGeu z@atW6mDW+33Dj_C*KVi6<~Ck?3Tq&S-9zm>%Mu%|@u$oA+<CgN6S6(!$O9*<FazcZ z*0;A2!x`Wu6xuo-#LLS`LHS3Uq9VCOgPD!c@}+xo31E1-eE1XctUt2g43~l@G!pWs zNy-8hH1()$ilCbCaYSba9D(=Sbs-5uq|SLJMAS5JE}G6FU#w@AGd-@gg>&QV(u41+ zbPY6=D7^J*F_*?gF<h8fYJFt&@pW<8_mCr=2PVc&y!tkFG5U-$CaU{518Uf1dwBsP zCWH^Jnvt>~vN2l5-gY(qjY*qd(*KY)X{7eDe1GOCVm4C4<9T*Pm;8JjHRWZv1@f*q zcGQ~xcRQW`4%wW_W%Ykea=c6z0bd|7OhRhve|AH@-t0X#nV9}W+m-gDN3#Nu0vJ=p z!}d*Pc0IlJLgisAi$6mZOkdcDH*Z@v*K{e!T$;?!d5pzic5Ir$w-~Uosme_s>Z=is z_(Eah*}z@kSqYJA(m%fB_yymBy}QR<#9R;V`w}Tp0LKNU%!->BoD?Is9(Y!5PxPHo z)zUqC%FTyo<uGWb_wrAeHbolgG_9re&#VM^`0ztQw`m(8sg*+9E~!uf@+Nrf%>(PR z{*}2mGBr9G5e@K{PQ%OcB~h4`@|uNutoT&eC@8dXOBB_{e%Ca#hX`||4&zIb)2=vh zU=^J=@M+z#_@@1aFW5ARomEC5Wz|dPpXSUR5p5E2i8shJX!I>+MCWJyoTr(d4c#_* zrp5^0!|R2+Gtx=((76O0X{Qn%ovG}zB@Z|1!XsdXQPMgxjzQVnu_PN;o@~a@tV`cL z4$i(-E?<eSox|$*whPtFQ51OPVFKS=XMZ6#N6Uh5w0%+sXeDcvl<VA0xOR|`)x2CB z20UfMa6bUprnhRN98TVjk*Y?@yoE58zoOsiq6B1cVg{e4JYpVi9pf_g9^<hMt3OUm z^H~`P+Yk7UB|T>HCRv*hO;BdW$indHMyxTlcs^1AY&9pSCZJ}kiZ$Nh{q&LfXhP^L zJtt$|KyC>6*%UI=DL$mbMu)*Bn)U6DccuISjqnAL^~FVz_LF9g915DjAyu)~RUwE# zCec(SfGe_5jcq~W>w<~9^UcW2UgFkDq6el>gLgkjC-G%*qT_uRxxwd2A(8?2KzERA z5)pP%xEzoyAGfm+D$dzTHZ;j=Mso4-j<7HHIlO_kl~Gr+o(CBxcO1OXjZ*6Hyd$!u zuu?ek!`9Z|2qn4Q0V2Q5c*eo;yzJeG+3jf>OO>OJM)4B!ke+kq5ivHfSM>taG!jvB zC;9iF&}zCb{<E!AvJ3t4_mbjo{ZcYV1m+svf(6o4jDvPkC;)Hkx-(TYTzKL6j(3%s zXh&=XDxc`pR<jP{6;V%*05d`;s8onj5DQh@IRNVUFzgI@(WPS@ns-#(_@sdr&XED? z2E3FIa?gYbs(#LD4%;k?r%|+Kbkq9C5Oq~<e03kZZ4Wy<>ft*z;4MSb=PurJ!9%s* zr{|g&=CYIfceZ=#G=KQEG*6R&97*^EuHX&hngVwX$dMFe=tn5qLt-fbiV%K}gkeaa z0ghP8)3t$6FSCU5obtHdu5hm7AeK76hpZRuT6zK4Jd2a5jlBvliHkbFExslhR(Iwm zU-4~%(d#K`3%LqaSC_+1`~naOR~<g$U1dEE)BQ5RS7Hb6Dz9V9U)9{S!Mnq#z}UK% z>I*g}d$C++i1{g9ZmPvO@!nnUHl?A_XOgZ2>HEM_?z%#A7u9f?^K02hPvkS5z?l<% zmzkx_T%wVdcAg`Ua;-Dz(M8-WR)XWpQQBR)M*j2{x6sP#4ck#`TVj;fSv~r~R3c4z z^P8;t>-9TM&9qj8f)RPbqxe*W!iLfnRF^?&*G#R8?stE;cKxW3E9dD|*b4HK#aRRo zmxsqN_{qhi#FjgE`t*1KO6xmjX1?Se`~J?T*Xs^H@gQQT5<yI?4y7_WAAWT6IUB|+ z`U>cP$IeBxat^h{nmR_a=?_vWCVUK{?i^-q6je_<Jl1d`2TOz<tP%X))7xxc364ZK zQjPFggk}pFIOy>yNbibLO~eNB{GJu6`h21$>O>ny8qY-Vj;iVW7W1gMADUBKL2k(+ zJbgaj5oPLnN%n!m^v1Ww!{tW1Ha;H=vAFTOuggkH$wN=-xBLsSEOP#>K$lqL-1Pf# z<9@H>hR~I*xSAC%K9U02L=-{Iau-vmYt97*VrB2}99)+^C&_jWMH>&w9bKi=FBCdj zyc0aUGqB!bDgX3jbJ+fJ478jN$R!`6mp&B-UVWqn66$^L+)RzF`MP-j^vTjM&S^G1 zbZp+`m02!<w`cs!wN9;*?W6EcC*g8aRt$zF`e7Y_v!*fYD>0b_eoaGI^Mv<<J*3^a z-|ODAPStM+0yUU=HRPXZdxQoVG1{I4p-{f}j49V$-P6q}sczvFZ405}Uv%^_%UO7{ z7XI8F{^-ZnlEXk_g1@epR3hsinYd$?B1J`jfo*&2V1rZ}rCMFZQ`)jx$v^ZDH-{e} zSRlSorX;7ClSL7^(*ZD?_0XBGrZ@;nl7iKGnrW@7bdqh(pB{^Tzg4<k;?tkkv=~Rk z?git#8ArfEgl3(4SisSEq5E+s46eXPysoT|lv_Jxe^L~%i=kQH*D)zd+!C97YaIox z!zF@cdh3RX$dOzNr^$*udiEh1=?2m+z7etnw5Zwk6!Zg5XLRxbXVXG1eoPhL4|z7W z4joizTvDr%86?IMzvB|S4_MZcDI5oivBB+5gKF9Jn*SNfe+#YhniP9(TSi(C$&V}z zoDn@`D(i|vxNgvhUZS0#icd{;e$&zQwXEd$5z0_?h_vHR^%S&az)NHilmnS)x|&fd zxoA4VZz)4CNpAmf2L1(QS$&1Nd?);z`7H&kc>2A^tJQsJJ*S~%rKh9bBw6zQa;i2L z^!{4V^$(fVuK<djc!AmvhEzLJrzQLI`aAJ&kpB@<{0juqPzE*GP$o6d{tt!wF9?P9 z^)FP#wXE+Sa>!QCiC)1l=OwlY3BL!!(0TlVsXUMg{Grf;o!c+0jN25uv@{q=odVqI zziI*e#gG4ZNc>epA<+w&;LYDpoqvUh8k&Ehdz$xC^Z#ny{nw}ddhi>_ctw#6v_d2N z2XN%CZ+(5x*8TOlx>rNvzh}q~6M2F+O+HNlgyD?=7sF+LTyrR}8iXc<$z-y>dsF^; z?T`1wuYCKOZTgxA`1iZ;Uq8`*L4;rg)6IWBlh?U2{NgNzJK3KE!H)13H~!yG0z9o$ zWT8Gp#H?JO_I`09aJFt|6(kjQ_NNc~=KEhRQyt`$4;@W4;z9uG%AH;4O*EAC3O*eK ztQ>%WnB3&uNa9mLy6spIAKMA1FJ_x{M}N&y5c)qY$A2&G4^Hx5P{Kr2(s<qzUch!l zI~8gmQWqER-RJ!F5&@Hy7R9|v*xw~~{aPVAH>9F-EW_ah{ejMP>XR?gFheNGOvTTK zIvNtR58ruSYlQqw(*FAC=lDx+pAC0fz+p;`)e9*j?qOAw2{j55s3UPo1dz^+tH>pO z@$I4ilN{`%3B=DJXr<J_xLkw!Q^wYG7A$*wynU!zV^}-lJPwA9dD!w2ovmI*!dL3% zeYP0?AaQS?(;UH6(K@;M4DXK@6OF_Pvq6U}lz@X$(d#8nRObYmGI{aei{h^%1t1Xr z9vFZYjd#J7a}baB{S54n@6m?(^`3-2E`6_cEzV6H+7kZ5+L217X$0ltAiATo4P+P2 zTSYyG4<B@f&%pmUOqw*9s;<gQtjU44bG+v*AX$x3#za>Z@VK%V54WX|GZ&1M5c&H< z=M@n*O#TZn2pPF80Ih#bcUN+~OQ~kWYMETqW^mpj?<rl1P8xLeIOD-VocZNXtS7PK zgT7tcJ~Nq$*7wkqw)%QVw#VZ&&UtLY6ycBtWwvmQ^QBc~?c|s2tTDj;Yv=xRI6F7w zU$O(iM{0NVH1nX)KAT2<EOsGg&`o;8ELyR0{Tye3*p5TA{aZ}>e@Rl&8$S!=&Ty7^ zhUxX6iFOeaLJ!?w4?+C@8*vXLP&oRUqzLsv9|HQ1WWVmF=A!2=6^!k`KeDp_^{6W2 z5o-QJLN>{1@UJ@nUNxNt_796G;P!f~#*2r{{00X4Um3~I3a<oD*(I@of5IwKME&4g zFsW(|a7+K^M5(0pxBBBL=;)@u)?3cNAtEBiM})2YH!70O0IayUxOaGXS_%OZGxYY> zHB)LM%31wofIJgDyU=HeC(97jzW}JQ=KfZD!f^gUTCqu&7hJ7fjLzt~>6V5Sg?AlG zh2}|m*@f?O%LQBA^ijaAm|r|y#5%j;t&St;jG7~|Yz3a+rV|>45ZuWEm5_VG1t<1o zKkeG33d6wQ=WO++=^+cd&;*3wX>ALt^$Q}scQwpXV^_|3t#YyjE3t>ihdR^at~Dc& zKo*tOP`}8R;dT_fk<RCLJuKZ&=Dzx^A2>|QF#ol$)9Ar|)rejMIYuWW1ikjA$bId0 ziYnij(ha$jgYP_dolLC@K!sOK3vyXW35fX|8dA~|N%+kcI}rt(s7x2(L)1z9^9AAp zhiEtTwy!%B<#OnI>FaDs8Ss8jTpy^DnU#CIE2PG(vpYO8l?h(mGqG(WD95+gN6)zA zldhF-+FKK%-tG-${8(=BU^E{mp)jD~=f{_nl)O&l#nWSR@j2c;Ila$m!GcmXtA0$k z=E8WvJ4$shlONSq30MA%+Ch34#&oukV}!v|W*OKSn`6k2lBiaG7^Y`XdqLf9Tb-CA z`63v?q?2}~6nDkUmtHSFpe=Fn)79M!kuM!2(#e=gEsW3|-hy3>U8jGsRv-};8a#4T zUIVqg{-)e|!LaN;TJxNjyqY!UyY(7bmC@~S`Up^TKClIw_u9S&2FSm$0KBR31azc- zf1=)AAJF02OKE=w@vA<zx}ejK{N=aS<^cPM%~qzgKE=uBn~$%S56k(!Rr_I34r6#s zo!LP#OvVj!N%(#xv4uO`1~)NoD*R`4)FK7_Zd=bLuL+!)3jckdb1IPOiXi1$PJ}vh zp+&Qsr;)W#I6(Dz-3wNu{Kl2EXJiX)%$DCfg%kUs%Lwz)gyvQwzguyu-~#eSK^~L) zJ;eH8GBYaGo(WONmQh#CialF1{Iiz_I_@`(gV#Vhg^|TKnh#;T(1?~&n<80U>MFiz zUx~nzI`>IC0pm(><j;fa-!pEjqhz|W^_T+#G-9F~WaYdswX+#cIKl?sLRk{HO{8s# zUKaj%<MHAkS7jZME3i(_)S)uccP_f8hBU}3N8qwRuTj4gpzGgABonHf@QC5fdcgTW zLJLyFf6E%+Pcg^oCdxo*i4Bv0)aTDCAM`@hSJd~B-G}#FxhG0GpuH|57QZ+#jVHSI zolH&)H#M~=hf}k_vcjwmHwc>lSfZ=)c!rot0B<oYkt*^2vQvCZasMW+gdOCEQx<eM zyM+#(rI@FSPwM8@n`l{g&-h_Kqxz@>M^df7Kj-W)mYq;ThhmxN<<_VsHCVR^cw?yX zZ44sLlzay$^R|R_02|j_{MxhZ=TJM{NObxYmIBe46swfi?D%q{1@Uw|s{m8V2W85D zN85%RTxu3ZtI_hPZ1KFdG&bTLeY|#+cu=WFlOt8R=ah|bKui9Uo6LZ+n^#$3(i;34 z=^bXq)i$^l8`FsVzwUoO>4YC6{4e3=69t5voR}aG2<k&gL(!VOC&3fu$&)R+mtxa% zG^z#X(;Uv;NC{(*q*HdD|JHbvTH(L}v$Sq)YkGjoQD$RbVLe}UzR!s>53vcSYS#U> zoDr_HkJi<J!^I#}gjH6XRegB2b~m{aiBa=fj9zAs&eqKeZjbVZyyMB)4jQAVN`lzw zd?$7568=&b*N@)oE8b;>;fw)^SmH&kWo$N=pU{!=on*`o9xOoTlOyr1r2|RgFspmb z+GTdyp8^t`$u?r_iMnS$4p@Y|?Sr9%AOX6Tm;=Mz@+FSnxSNZ=-DjIRhnh?{s~9Ll zi@Hm+JMOkVp0CcA=-(gO;MIlx_+#RB4g<Yuk-c>p``Oy=1=(7EnGg3R?V^eGhUYY0 zKuipROeV>f&3c}Ikx{W4>zcKuiR{pFR=Pjaz3lyb)TU@JbZqc+`wf}C+o-DR9laOr zR$td;J>SpIift!04R158IIL(*HHYeQ65wmSXW$2C`CO353h}BlY{wh%yFbcLww`F- za`H(cj`a-3Pa#abGJiiO<m!=e@afXAj4xYjJ}y*)efzjc(e^0#mY|jo&&?1POY6}l z^PY?h*DSXSXnIT@QzxTb-B5bJ60sR@&v0^&_>vYK8j_`iQx$S%g4DsgaV#+TjDL1N zPJ9}hx9oc0B%62FCSK8bdGnbtcQORJ!D3f~q=z%b3gR}A&}j?Z^|~-tEeiU{wyXDE z>-4c+%K%wyGr9IitC`~@3(${CK%~0Yy5)M+R=vrX-;^bmes-Jg#YzTR@NR$Ewx010 zo1Cg@WOUukz#h~1WU1H<*39QvI#p64Q-HT@uZKVQY!;6;b~v)!PLp5uVnVR(b&f1^ z<!`qe04G3sJVUY)?8m4?cA@KeG3+8yO>Ntr)x=oU;^BOiQY&tyh~>@gt|r~1Kd1EY z)9$sG4VGC8_K;84GveBIZYam{iT7f>LJ#S#GrUven>)#~fll$0W}DKBPwmHi>A2^0 zs~w>*we|2+<5i8;XSZI$TYL!hH>S;(N1wSX#-E?v>4nsq0r*vxSrBL&b1!s3J`)FL zkGpVG0shQ^7$)(<NnZjKQbJwGWrBUytzB2=?UrnRqBUPW3)SF#A+OMQK}FNhC;`;Y zFZwci!z>kS6*nKxH{7^0X)VMFZ{Pi;!K}s*DX6Iht$07kB6mQ1#8TO7Zo<cZ^MEe( z!kwRR*>+NS)z;>N9b5HjBXo_d`Ct(p_vTFs_f`(klP0Q4CMm=4Po}(PzUuu2FDkDl z8tHk9M~3z3rZG)jrTaxwyoWc=K3s1p&xz`8^U8fzavYhS$G_KhT6NpTF84&uv6TYI z${~yvp@4Pj6RXR~Fq2o}$C53|I%?D(-sQ*{t-W?tC^9|QXnFZK;<e2y-RjJJJg!Tm z)vrGG+$8LybN{^3@=`uf27xXpHa^e%Anci@_|8d6UaN=d%GmQJfUPyw`~Cs>)I$X2 z!}Z}rMLqT{fta|7YZ*+}s9SSUAc#q0xCws%RL(y&d=0{c>APcNA}co2rV0mCV`GD~ ztJ4&`PSpz&*IzOoV*y$5^cxK`N8R?a^VdBM2Dr7~n7}%%%q{f*su>QSJmSx6Ob3$s zl%IL0Jq8Jzk9i=zFtOPAM5$}Pb9I*}KDgBk$9d$5uW!EzszK6$7@a-M3`3NVloYhG zqB(AR@dgG%3)~zpc}1==A$D5iT0SsX$!Lb7^DhBX?{)ldckjU@^{>n#w*Ym7wOQYv z-^I1yVQ)#j*wiw-EOeMHAE+&+qWB?c`coW;6vc*+f(=D#reGwDx}b>ZPN~p*k4vfs z*Ly^Z;6{$pPYE$7CynGLg)^zTTM>n1*!DJMgsqaBG%n*BLvVS#f~zTZ`QZ4PzP%!& z8I*V;A*8?WP&P~<<`6EiPsBpAto}|GId--FNn958fbG%-Hlj2t9E86P5M9X;et<+U zrwh#F`SO9-eeHzZMYQr!zm=LlNg4gPo0)wvL`$ogkbj@{^!7HD1qNtT+A82~o+s)` z-QIfSp}_qqfS9}xwqlU{X|u<<>~VJb$Mu|^{&<dg2(||#mp(Px_qkBKeMlv|$JFV5 zlqoJ99PinP-N&$tQK6&=rNO!Zf5%~n|3TL`hu5`jZ#PbwG`1Vtjm@U9tsQg6W*ghK zZQEvJyRq#r=bU@by|?H4{kNXIpJ%N(2i`H}obMciW^g}zXE5zv7C-yb(ru59KPl11 zy|VkwB4{g{Axe5j8wzXL!uM`}kMm0{z=4WA+(C=<=VmLGQ-W5sicubKBqzSm@ta;j zNqoT?(yHU{U%6RsMUf!wzI55_uAnvEM)_Q#7ce5bUW=^X;vpdc`YU4Ax>B2{8FOF4 z&TA^KfQ-;@tyy><>w~=6fjDzP-q-t$Stca$TEV;Dm7s)=o4V~*dF;#NH0d+)0HmaR zABkAvzj_Er)`Lj_<TVe;*=k~YhKg<|V?P|$kM~s+UsW5uoXb3P-EuX;5J+o>A{ggw zHX+F+-t2-Nmw1uBo~8Q~#x!URpQG@;9(?2G5N@^()3@=cFlwx}!vU8F*c^db-&}?; zb>5U|$P(%&{~9JMm^f2XjaXxspXdl+iw`<LSr=5^^K7hl6Z0)1P<e}QtoI`zv(ygh zHov*`v^e2J%?5F0TL`^-=gap_r<R@!mM!DbVmIdR*0rLQlDAk~{aqw;1DUka{y_fy zcH(wXLAqcVcAP5QLix(+02Kdp`cEfmweM)lGWxl<w$AkNBQsTGX%*eKXzPu)B6(-q znZ$z<{1Y#W>@-OY5Ts0YyQE+2NP)!KX^r4xP_KCak6(#`l5h4POh^5jS<C)J-ESYz zrC%VY$zQzVi4>2|>n3nzebc&gzk-+HSYd_a7A7<0--GK|sQRX@YJ^N8d++;}V@{WV zZRMD4MnbEgu+l_UDR3`|d+ETy`+?Xv-&gsV&=|Vh)sU)I3Tc<FQEh4GVo$q=$BoPf z6VA;`WN<)U^+9K~+F-cvceD^Z)pp-vtrO=pW?|xn3beTT-8&o>+*4}YbZBV>9qJD& zcFx6LtY+1%Z?2+AFj$)7ifR|lU+W?DAU{TQM<z0W2cYUdrlcPcEih?YtecZq9pZ+s zcq~E(Li;y(p6WT>ke^tz1zg1ijnwUg8!J|<PiWH2Maz#I$^_$6#5)*$NfYj$3eN%M zAl<HTGQ^!H@x;W({0aHM)bHjnPBdz|jxI!e(`Yv;=WM+a7T61dmGaf?1pvR*C)>N+ zSf5wm>UAlK1!iT33-;bA;E~09sqCmFG(*Wn=SFd@AFYMWp7XgO8$8x<A)UvK!FK0s zDvw*ed<Tru6Izi;IDO_@{`?yR!p)g?Wh<OJ^9l3w%UDIEt0wxK%)Y-7Q-(`lGMM@) z(_F3@x<Zg#7$PN6Upm@VmHV<MIIBs}0w!rTM{{YJ8b!$zI$LmMj;6`eXUw1bG(Vk= zV*&(yZs!Q|9eh1$>+1^l;rSAlTnxvXz6h-~&jj(0p<sPo_e4b+BPr{aG?;5f)-sRN zTTNye15VwpN781CSKx0hH0USfmx@8+CNN-8goGjr<bE)Hf}35{(IA#|tC6n&<D92Q zkx+O^My{XvA(>J{_BIn<O0S2o6@VoXl?KHUVqOA^6OW5caHxhkQ```!_l<`6vz0ad z43Z~K>f19YwHHMVxBy5E!6IwWaPX$ppOai3N5WbH#umq(xq;zlU;>wD4<hl!AQw z5frJ}mcH%}Y8wS$)za|zI|aSx@x3#y1xG%ecwdpXf1o8nSz8ixWnxT9>{t_3>aN~G z`<X6M>&%`1J{6y8vGfdyTHM`zZa4$p1hS)BEoIENUgp$lvLgo%F51({M9i6Z*tGH{ z{IJ*s1$uP3pRDkBa`9f;hvVpJNM$1h!B)4%DCft%P8ZL<2<H_}`4SvF9rzR-<9=z- zL5G$q{08K3mUw|%THV+7`M07p+}Vt_%#MUFfduN`+y+OM@|?gW<@T4<dWUG5;vJCn z97brMXCu-kC-0XU)nT(G-3MM^+LC9xjSkq`7&jf<FBOU%Im(U2?$gFPo7;-V_VjOl z;oh^!2nxUSV^8>9{UWri$VI~oFj?g0S4y^1-%XgGcdh=F_@B$F379^8AsFVc(j}(R z3I)=AuP;y3U(Rjl>I*JF%p5xFe#$J>x~HM%nhdxe$Fy(Ot(KoJVAMq{yLr_^KUCkE zIU6wZ1Rkmk%SA1Japk#7k|Rv<8-ktj^?b>zphik8@4R6n8H9^$8;oslo$xS<gFUEu zWILk;Bp$9q5#}qHKg8CV()PsrpR{~d#{nL91|iN;*RvmB7oS?aQwVF5a7_>^ZlY#Y zv$S##m%j+NstahL!}5{pqeg7w*@0#BYqejm≺8>u0!IU<fc5D$&;}c#M0?YVu@P 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�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%0Dv@qbc+2yF?S_v{a4f`l{2rFRNu`=h8++5Wcc! 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⁡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 S&3n(Du8)$666Ios0sjZn>ElxX literal 0 HcmV?d00001 diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/system.svg b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/system.svg new file mode 100644 index 0000000000000..0aba96275e24e --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/img/system.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><path d="M433.41 29.92c-9.81 5.14-15.18 10.04-19.15 17.74-1.87 3.74-9.57 43.43-17.28 88.49-23.58 140.32-43.19 243.98-72.61 384.3l-2.57 12.38-10.27-10.97c-19.61-20.31-45.76-25.92-67.71-14.48-14.71 7.47-24.05 20.31-34.55 46.93-7.7 20.32-9.11 22.18-15.64 23.58-3.97.7-39.22 1.4-78.45 1.4-67.94 0-71.91.23-81.72 4.9C7.54 596.8 1.94 631.82 22.49 651.9c13.08 12.84 17.04 13.31 88.72 13.54 106 0 125.38-3.04 145.69-24.05l7.94-8.17 6.3 13.54c12.14 25.45 28.72 38.76 51.6 41.56 24.52 2.8 42.49-10.97 56.03-43.19 13.31-31.98 39.93-147.56 63.28-272.93 5.6-29.88 10.51-54.4 11.21-54.4.7 0 26.85 140.09 58.14 311.22 31.28 170.91 58.37 314.73 60.24 319.17 4.2 10.51 9.11 15.87 19.85 21.25 18.45 9.57 43.19 3.04 54.4-14.01 5.14-7.71 7.24-16.34 13.07-57.67 12.36-85.22 33.84-204.06 36.87-204.06.7 0 4.67 5.37 8.4 11.91 14.48 24.75 37.82 38.06 66.54 38.29 29.18 0 40.63-9.34 72.15-58.84l16.11-25.21 57.2-1.17c56.5-1.17 57.44-1.17 63.27-6.77 7.94-7.47 11.44-19.15 10.27-34.09-1.4-15.88-8.17-28.72-20.08-37.12l-9.57-6.77-57.43-1.17c-69.58-1.4-77.51-.23-94.33 14.94-6.3 5.84-17.74 19.84-24.98 31.29-7.47 11.44-13.78 20.78-14.01 20.31-.24-.23-2.57-12.61-5.37-27.32-8.64-48.8-19.38-69.81-40.86-80.55-22.41-11.21-48.33-6.31-64.21 11.91-14.47 16.34-30.12 56.03-43.66 110.67-3.5 14.47-6.77 25.68-7.24 24.52-.23-1.4-26.38-142.66-57.9-314.03-63.04-342.74-58.6-323.83-78.21-333.87-10.96-5.61-28.71-6.08-38.51-.71z"/></svg> \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/manifest.yml new file mode 100644 index 0000000000000..4f03b0d37a444 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/manifest.yml @@ -0,0 +1,32 @@ +format_version: 3.2.0 +name: good_content +title: Good content package +description: > + This package is a dummy example for packages with the content type. + These packages contain resources that are useful with data ingested by other integrations. + They are not used to configure data sources. +version: 0.1.0 +type: content +source: + license: "Apache-2.0" +conditions: + kibana: + version: '^8.16.0' #TBD + elastic: + subscription: 'basic' +discovery: + fields: + - name: process.pid +screenshots: + - src: /img/kibana-system.png + title: kibana system + size: 1220x852 + type: image/png +icons: + - src: /img/system.svg + title: system + size: 1000x1000 + type: image/svg+xml +owner: + github: elastic/ecosystem + type: elastic diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/validation.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/validation.yml new file mode 100644 index 0000000000000..fa39a1d6f18bf --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/good_content/0.1.0/validation.yml @@ -0,0 +1,3 @@ +errors: + exclude_checks: + - PSR00002 # Allow to use non-GA features. diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index ea50aaaf53eb8..58a514b21a31d 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -642,6 +642,24 @@ export default function (providerContext: FtrProviderContext) { expect(body.item.inputs[0].enabled).to.eql(false); }); + it('should return 400 for content packages', async function () { + const response = await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'content-pkg-policy', + description: '', + namespace: 'default', + policy_ids: [], + package: { + name: 'good_content', + version: '0.1.0', + }, + }) + .expect(400); + expect(response.body.message).to.eql('Cannot create policy for content only packages'); + }); + describe('input only packages', () => { it('should default dataset if not provided for input only pkg', async function () { await supertest From d87a38f6cccd1ed16ff5443cf19064ae57fd52d6 Mon Sep 17 00:00:00 2001 From: florent-leborgne <florent.leborgne@elastic.co> Date: Tue, 15 Oct 2024 19:25:49 +0200 Subject: [PATCH 55/84] [Docs] Resize image for dashboard usage (#195914) This PR is a small fix to resize an image in the Dashboards docs to make it look better and not blurry --- docs/user/dashboard/view-dashboard-usage.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/dashboard/view-dashboard-usage.asciidoc b/docs/user/dashboard/view-dashboard-usage.asciidoc index 5ac7e72c3e246..8520c6348829a 100644 --- a/docs/user/dashboard/view-dashboard-usage.asciidoc +++ b/docs/user/dashboard/view-dashboard-usage.asciidoc @@ -6,4 +6,4 @@ image:images/view-details-dashboards-8.16.0.png[View details icon in the list of These details include a graph showing the total number of views during the last 90 days. -image:images/dashboard-usage-count.png[Graph showing the number of views during the last 90 days] \ No newline at end of file +image:images/dashboard-usage-count.png[Graph showing the number of views during the last 90 days, width="50%"] \ No newline at end of file From 7b9ff3d90cabe7e7f9e3ca4e6ecec31344afaff4 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski <tomasz.ciecierski@elastic.co> Date: Tue, 15 Oct 2024 19:28:55 +0200 Subject: [PATCH 56/84] [EDR Workflows] Enable automated response actions UI in all rules (#196051) --- .../common/detection_engine/utils.ts | 13 ----------- .../common/experimental_features.ts | 5 ---- .../components/step_rule_actions/index.tsx | 23 +++++-------------- .../pages/rule_creation/index.tsx | 2 -- .../pages/rule_editing/index.tsx | 2 -- .../rule_management/utils/validate.ts | 11 --------- 6 files changed, 6 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index a98ca169a41d7..e0cefdebecd93 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -93,16 +93,3 @@ export const isSuppressionRuleConfiguredWithMissingFields = (ruleType: Type) => export const isSuppressionRuleInGA = (ruleType: Type): boolean => { return isSuppressibleAlertRule(ruleType) && SUPPRESSIBLE_ALERT_RULES_GA.includes(ruleType); }; -export const shouldShowResponseActions = ( - ruleType: Type | undefined, - automatedResponseActionsForAllRulesEnabled: boolean -) => { - return ( - isQueryRule(ruleType) || - isEsqlRule(ruleType) || - isEqlRule(ruleType) || - isNewTermsRule(ruleType) || - (automatedResponseActionsForAllRulesEnabled && - (isThresholdRule(ruleType) || isThreatMatchRule(ruleType) || isMlRule(ruleType))) - ); -}; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index f18ddff6e4f17..5e438669916c6 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -52,11 +52,6 @@ export const allowedExperimentalValues = Object.freeze({ */ automatedProcessActionsEnabled: true, - /** - * Temporary feature flag to enable the Response Actions in Rules UI - intermediate release - */ - automatedResponseActionsForAllRulesEnabled: false, - /** * Enables the ability to send Response actions to SentinelOne and persist the results * in ES. Adds API changes to support `agentType` and supports `isolate` and `release` diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx index 06168ce97a2c7..ca79d429e43ad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx @@ -15,9 +15,6 @@ import type { ActionVariables, } from '@kbn/triggers-actions-ui-plugin/public'; import { UseArray } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { shouldShowResponseActions } from '../../../../../common/detection_engine/utils'; import type { RuleObjectId } from '../../../../../common/api/detection_engine/model/rule_schema'; import { ResponseActionsForm } from '../../../rule_response_actions/response_actions_form'; import type { @@ -40,7 +37,6 @@ interface StepRuleActionsProps extends RuleStepProps { ruleId?: RuleObjectId; // Rule SO's id (not ruleId) actionMessageParams: ActionVariables; summaryActionMessageParams: ActionVariables; - ruleType?: Type; form: FormHook<ActionsStepRule>; } @@ -79,15 +75,11 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({ isUpdateView = false, actionMessageParams, summaryActionMessageParams, - ruleType, form, }) => { const { services: { application }, } = useKibana(); - const automatedResponseActionsForAllRulesEnabled = useIsExperimentalFeatureEnabled( - 'automatedResponseActionsForAllRulesEnabled' - ); const displayActionsOptions = useMemo( () => ( <> @@ -105,15 +97,12 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({ [actionMessageParams, summaryActionMessageParams] ); const displayResponseActionsOptions = useMemo(() => { - if (shouldShowResponseActions(ruleType, automatedResponseActionsForAllRulesEnabled)) { - return ( - <UseArray path="responseActions" initialNumberOfItems={0}> - {ResponseActionsForm} - </UseArray> - ); - } - return null; - }, [automatedResponseActionsForAllRulesEnabled, ruleType]); + return ( + <UseArray path="responseActions" initialNumberOfItems={0}> + {ResponseActionsForm} + </UseArray> + ); + }, []); // only display the actions dropdown if the user has "read" privileges for actions const displayActionsDropDown = useMemo(() => { return application.capabilities.actions.show ? ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index 500fedb4d0005..28d137ac522ae 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -789,7 +789,6 @@ const CreateRulePageComponent: React.FC = () => { isLoading={isCreateRuleLoading || loading || isStartingJobs} actionMessageParams={actionMessageParams} summaryActionMessageParams={actionMessageParams} - ruleType={ruleType} form={actionsStepForm} /> @@ -841,7 +840,6 @@ const CreateRulePageComponent: React.FC = () => { isCreateRuleLoading, isStartingJobs, loading, - ruleType, submitRuleDisabled, submitRuleEnabled, ] diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index f8db919ff9416..9151e6965bd11 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -348,7 +348,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { isUpdateView actionMessageParams={actionMessageParams} summaryActionMessageParams={actionMessageParams} - ruleType={rule?.type} form={actionsStepForm} key="actionsStep" /> @@ -362,7 +361,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { [ rule?.immutable, rule?.id, - rule?.type, activeStep, loading, isSavedQueryLoading, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index 5ff9d2d97f2b0..a61c28b5ced3c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -8,7 +8,6 @@ import type { PartialRule } from '@kbn/alerting-plugin/server'; import { isEqual, xorWith } from 'lodash'; import { stringifyZodError } from '@kbn/zod-helpers'; -import { shouldShowResponseActions } from '../../../../../common/detection_engine/utils'; import { type ResponseAction, type RuleCreateProps, @@ -59,16 +58,6 @@ export const validateResponseActionsPermissions = async ( ruleUpdate: RuleCreateProps | RuleUpdateProps, existingRule?: RuleAlertType | null ): Promise<void> => { - const { experimentalFeatures } = await securitySolution.getConfig(); - if ( - !shouldShowResponseActions( - ruleUpdate.type, - experimentalFeatures.automatedResponseActionsForAllRulesEnabled - ) - ) { - return; - } - if ( !rulePayloadContainsResponseActions(ruleUpdate) || (existingRule && !ruleObjectContainsResponseActions(existingRule)) From 302ac0d336feb861522c9ca3f3c271e172b86ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 16 Oct 2024 00:58:43 +0700 Subject: [PATCH 57/84] Add support for GeoIP processor databases in Ingest Pipelines (#190830) Fixes https://github.com/elastic/kibana/issues/190818 ## Summary Elasticsearch has added support for GeoIP, enabling the use of paid GeoIP databases from MaxMind/IPInfo for more accurate and granular geolocation data. As such we should add support to ingest pipelines UI for making this available to the user. * If the user doesn't have enough privileges, the "Manage Pipelines" link and UI won't show. * Users can add two types of databases through the UI: MaxMind and IPinfo. Database names are predefined by ES, and the user cannot enter their own. * Certain types of databases (local and web) can be configured through ES, and these will appear in the UI, but they cannot be deleted as they are read-only. * When configuring a `IP location` processor, the database field will display a list of available and configured databases that the user can select. It also allows for free-text input if the user wants to configure a database that does not yet exist. * The new IP location processor is essentially a clone of the GeoIP processor, which we are moving away from due to copyright issues. However, it was decided that GeoIP will remain as is for backward compatibility, and all new work will only be added to IP location going forward. * I left a few mocks in the `server/routes/api/geoip_database/list.ts ` to try `local/web` types ## Release note The Ingest Pipelines app now supports adding and managing databases for the GeoIP processor. Additionally, the pipeline creation flow now includes support for the IP Location processor. <details> <summary>Screenshots</summary> ![Screenshot 2024-10-07 at 09 36 31](https://github.com/user-attachments/assets/60d438cc-6658-4475-bd27-036c7d13d496) ![Screenshot 2024-10-07 at 09 38 58](https://github.com/user-attachments/assets/7c08e94f-b35c-4e78-a204-1fb456d88181) ![Screenshot 2024-10-07 at 09 47 08](https://github.com/user-attachments/assets/2baca0bd-811d-4dd5-9eb6-9b3f41579249) ![Screenshot 2024-10-07 at 09 47 20](https://github.com/user-attachments/assets/74d8664c-8c73-41f3-8cd5-e0670f3ada77) ![Screenshot 2024-10-07 at 09 48 19](https://github.com/user-attachments/assets/9fb4c186-6224-404c-a8d6-5c44c14da951) ![Screenshot 2024-10-07 at 09 48 25](https://github.com/user-attachments/assets/07e4909d-2613-45aa-918b-11a189e14f6f) </details> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Ignacio Rivas <rivasign@gmail.com> Co-authored-by: Elena Stoeva <elenastoeva99@gmail.com> Co-authored-by: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> Co-authored-by: Matthew Kime <matt@mattki.me> --- config/serverless.yml | 3 + .../test_suites/core_plugins/rendering.ts | 1 + .../helpers/http_requests.ts | 15 + .../client_integration/helpers/index.ts | 4 +- .../helpers/manage_processors.helpers.ts | 144 +++++++++ .../helpers/setup_environment.tsx | 3 + .../manage_processors.test.tsx | 187 ++++++++++++ .../plugins/ingest_pipelines/common/types.ts | 17 +- .../public/application/app.tsx | 29 +- .../processor_form/processors/index.ts | 1 + .../processor_form/processors/ip_location.tsx | 131 ++++++++ .../shared/map_processor_type_to_form.tsx | 19 ++ .../public/application/constants/index.ts | 1 + .../public/application/index.tsx | 5 +- .../application/mount_management_section.ts | 6 +- .../public/application/sections/index.ts | 2 + .../manage_processors/add_database_modal.tsx | 280 ++++++++++++++++++ .../sections/manage_processors/constants.ts | 176 +++++++++++ .../delete_database_modal.tsx | 135 +++++++++ .../sections/manage_processors/empty_list.tsx | 36 +++ .../sections/manage_processors/geoip_list.tsx | 202 +++++++++++++ .../manage_processors/get_error_message.tsx | 27 ++ .../sections/manage_processors/index.ts | 9 + .../manage_processors/manage_processors.tsx | 44 +++ .../use_check_manage_processors_privileges.ts | 15 + .../sections/pipelines_list/main.tsx | 121 +++++--- .../public/application/services/api.ts | 37 ++- .../application/services/breadcrumbs.ts | 13 +- .../public/application/services/navigation.ts | 8 + .../plugins/ingest_pipelines/public/index.ts | 5 +- .../plugins/ingest_pipelines/public/plugin.ts | 12 +- .../plugins/ingest_pipelines/public/types.ts | 4 + .../plugins/ingest_pipelines/server/config.ts | 29 ++ .../plugins/ingest_pipelines/server/index.ts | 8 +- .../plugins/ingest_pipelines/server/plugin.ts | 8 +- .../server/routes/api/database/create.ts | 74 +++++ .../server/routes/api/database/delete.ts | 40 +++ .../server/routes/api/database/index.ts | 10 + .../server/routes/api/database/list.ts | 37 +++ .../api/database/normalize_database_name.ts | 10 + .../routes/api/database/serialization.ts | 94 ++++++ .../server/routes/api/index.ts | 6 + .../server/routes/api/privileges.ts | 20 +- .../ingest_pipelines/server/routes/index.ts | 8 + .../plugins/ingest_pipelines/server/types.ts | 1 + x-pack/plugins/ingest_pipelines/tsconfig.json | 4 +- .../management/ingest_pipelines/databases.ts | 67 +++++ .../apis/management/ingest_pipelines/index.ts | 14 + .../ingest_pipelines/geoip_databases.ts | 6 + .../services/ingest_pipelines/lib/api.ts | 15 + .../functional/apps/ingest_pipelines/index.ts | 1 + .../ingest_pipelines/manage_processors.ts | 95 ++++++ x-pack/test/functional/config.base.js | 14 + .../page_objects/ingest_pipelines_page.ts | 53 ++++ 54 files changed, 2218 insertions(+), 88 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/manage_processors.helpers.ts create mode 100644 x-pack/plugins/ingest_pipelines/__jest__/client_integration/manage_processors.test.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/ip_location.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/add_database_modal.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/constants.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/delete_database_modal.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/empty_list.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/geoip_list.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/get_error_message.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/manage_processors.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/use_check_manage_processors_privileges.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/config.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/database/create.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/database/delete.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/database/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/database/list.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/database/normalize_database_name.ts create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/database/serialization.ts create mode 100644 x-pack/test/api_integration/apis/management/ingest_pipelines/databases.ts create mode 100644 x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts create mode 100644 x-pack/test/api_integration/services/ingest_pipelines/geoip_databases.ts create mode 100644 x-pack/test/functional/apps/ingest_pipelines/manage_processors.ts diff --git a/config/serverless.yml b/config/serverless.yml index 8f7857988d77e..4249d8ff786ec 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -113,6 +113,9 @@ xpack.index_management.enableTogglingDataRetention: false # Disable project level rentention checks in DSL form from Index Management UI xpack.index_management.enableProjectLevelRetentionChecks: false +# Disable Manage Processors UI in Ingest Pipelines +xpack.ingest_pipelines.enableManageProcessors: false + # Keep deeplinks visible so that they are shown in the sidenav dev_tools.deeplinks.navLinkStatus: visible management.deeplinks.navLinkStatus: visible diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 02355c97823cf..6a863a78cff15 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -314,6 +314,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.ml.nlp.modelDeployment.vCPURange.medium.static (number?)', 'xpack.osquery.actionEnabled (boolean?)', 'xpack.remote_clusters.ui.enabled (boolean?)', + 'xpack.ingest_pipelines.enableManageProcessors (boolean?|never)', /** * NOTE: The Reporting plugin is currently disabled in functional tests (see test/functional/config.base.js). * It will be re-enabled once #102552 is completed. diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/http_requests.ts index d7c833ef85403..e9793791a394e 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/http_requests.ts @@ -73,12 +73,27 @@ const registerHttpRequestMockHelpers = ( const setParseCsvResponse = (response?: object, error?: ResponseError) => mockResponse('POST', `${API_BASE_PATH}/parse_csv`, response, error); + const setLoadDatabasesResponse = (response?: object[], error?: ResponseError) => + mockResponse('GET', `${API_BASE_PATH}/databases`, response, error); + + const setDeleteDatabasesResponse = ( + databaseName: string, + response?: object, + error?: ResponseError + ) => mockResponse('DELETE', `${API_BASE_PATH}/databases/${databaseName}`, response, error); + + const setCreateDatabasesResponse = (response?: object, error?: ResponseError) => + mockResponse('POST', `${API_BASE_PATH}/databases`, response, error); + return { setLoadPipelinesResponse, setLoadPipelineResponse, setDeletePipelineResponse, setCreatePipelineResponse, setParseCsvResponse, + setLoadDatabasesResponse, + setDeleteDatabasesResponse, + setCreateDatabasesResponse, }; }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts index 5f4dc01fa924a..31cf685e35533 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts @@ -10,8 +10,9 @@ import { setup as pipelinesCreateSetup } from './pipelines_create.helpers'; import { setup as pipelinesCloneSetup } from './pipelines_clone.helpers'; import { setup as pipelinesEditSetup } from './pipelines_edit.helpers'; import { setup as pipelinesCreateFromCsvSetup } from './pipelines_create_from_csv.helpers'; +import { setup as manageProcessorsSetup } from './manage_processors.helpers'; -export { nextTick, getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; +export { getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { setupEnvironment } from './setup_environment'; @@ -21,4 +22,5 @@ export const pageHelpers = { pipelinesClone: { setup: pipelinesCloneSetup }, pipelinesEdit: { setup: pipelinesEditSetup }, pipelinesCreateFromCsv: { setup: pipelinesCreateFromCsvSetup }, + manageProcessors: { setup: manageProcessorsSetup }, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/manage_processors.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/manage_processors.helpers.ts new file mode 100644 index 0000000000000..d0127943d7fa3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/manage_processors.helpers.ts @@ -0,0 +1,144 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { HttpSetup } from '@kbn/core/public'; + +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; +import { ManageProcessors } from '../../../public/application/sections'; +import { WithAppDependencies } from './setup_environment'; +import { getManageProcessorsPath, ROUTES } from '../../../public/application/services/navigation'; + +const testBedConfig: AsyncTestBedConfig = { + memoryRouter: { + initialEntries: [getManageProcessorsPath()], + componentRoutePath: ROUTES.manageProcessors, + }, + doMountAsync: true, +}; + +export type ManageProcessorsTestBed = TestBed<ManageProcessorsTestSubjects> & { + actions: ReturnType<typeof createActions>; +}; + +const createActions = (testBed: TestBed) => { + const { component, find, form } = testBed; + + const clickDeleteDatabaseButton = async (index: number) => { + const allDeleteButtons = find('deleteGeoipDatabaseButton'); + const deleteButton = allDeleteButtons.at(index); + await act(async () => { + deleteButton.simulate('click'); + }); + + component.update(); + }; + + const confirmDeletingDatabase = async () => { + await act(async () => { + form.setInputValue('geoipDatabaseConfirmation', 'delete'); + }); + + component.update(); + + const confirmButton: HTMLButtonElement | null = document.body.querySelector( + '[data-test-subj="deleteGeoipDatabaseSubmit"]' + ); + + expect(confirmButton).not.toBe(null); + expect(confirmButton!.disabled).toBe(false); + expect(confirmButton!.textContent).toContain('Delete'); + + await act(async () => { + confirmButton!.click(); + }); + + component.update(); + }; + + const clickAddDatabaseButton = async () => { + const button = find('addGeoipDatabaseButton'); + expect(button).not.toBe(undefined); + await act(async () => { + button.simulate('click'); + }); + + component.update(); + }; + + const fillOutDatabaseValues = async ( + databaseType: string, + databaseName: string, + maxmind?: string + ) => { + await act(async () => { + form.setSelectValue('databaseTypeSelect', databaseType); + }); + component.update(); + + if (maxmind) { + await act(async () => { + form.setInputValue('maxmindField', maxmind); + }); + } + await act(async () => { + form.setSelectValue('databaseNameSelect', databaseName); + }); + + component.update(); + }; + + const confirmAddingDatabase = async () => { + const confirmButton: HTMLButtonElement | null = document.body.querySelector( + '[data-test-subj="addGeoipDatabaseSubmit"]' + ); + + expect(confirmButton).not.toBe(null); + expect(confirmButton!.disabled).toBe(false); + + await act(async () => { + confirmButton!.click(); + }); + + component.update(); + }; + + return { + clickDeleteDatabaseButton, + confirmDeletingDatabase, + clickAddDatabaseButton, + fillOutDatabaseValues, + confirmAddingDatabase, + }; +}; + +export const setup = async (httpSetup: HttpSetup): Promise<ManageProcessorsTestBed> => { + const initTestBed = registerTestBed( + WithAppDependencies(ManageProcessors, httpSetup), + testBedConfig + ); + const testBed = await initTestBed(); + + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +export type ManageProcessorsTestSubjects = + | 'manageProcessorsTitle' + | 'addGeoipDatabaseForm' + | 'addGeoipDatabaseButton' + | 'geoipDatabaseList' + | 'databaseTypeSelect' + | 'maxmindField' + | 'databaseNameSelect' + | 'addGeoipDatabaseSubmit' + | 'deleteGeoipDatabaseButton' + | 'geoipDatabaseConfirmation' + | 'geoipEmptyListPrompt' + | 'geoipListLoadingError'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx index 58701ffb1dd64..6725a7381decf 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx @@ -70,6 +70,9 @@ const appServices = { }, overlays: overlayServiceMock.createStartContract(), http: httpServiceMock.createStartContract({ basePath: '/mock' }), + config: { + enableManageProcessors: true, + }, }; export const setupEnvironment = () => { diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/manage_processors.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/manage_processors.test.tsx new file mode 100644 index 0000000000000..81375d1e3ae83 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/manage_processors.test.tsx @@ -0,0 +1,187 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import { ManageProcessorsTestBed } from './helpers/manage_processors.helpers'; + +import { setupEnvironment, pageHelpers } from './helpers'; +import type { GeoipDatabase } from '../../common/types'; +import { API_BASE_PATH } from '../../common/constants'; + +const { setup } = pageHelpers.manageProcessors; + +describe('<ManageProcessors />', () => { + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: ManageProcessorsTestBed; + + describe('With databases', () => { + beforeEach(async () => { + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + }); + + const database1: GeoipDatabase = { + name: 'GeoIP2-Anonymous-IP', + id: 'geoip2-anonymous-ip', + type: 'maxmind', + }; + + const database2: GeoipDatabase = { + name: 'GeoIP2-City', + id: 'geoip2-city', + type: 'maxmind', + }; + + const database3: GeoipDatabase = { + name: 'GeoIP2-Country', + id: 'geoip2-country', + type: 'maxmind', + }; + + const database4: GeoipDatabase = { + name: 'Free-IP-to-ASN', + id: 'free-ip-to-asn', + type: 'ipinfo', + }; + + const databases = [database1, database2, database3, database4]; + + httpRequestsMockHelpers.setLoadDatabasesResponse(databases); + + test('renders the list of databases', async () => { + const { exists, find, table } = testBed; + + // Page title + expect(exists('manageProcessorsTitle')).toBe(true); + expect(find('manageProcessorsTitle').text()).toEqual('Manage Processors'); + + // Add database button + expect(exists('addGeoipDatabaseButton')).toBe(true); + + // Table has columns for database name and type + const { tableCellsValues } = table.getMetaData('geoipDatabaseList'); + tableCellsValues.forEach((row, i) => { + const database = databases[i]; + + expect(row).toEqual([ + database.name, + database.type === 'maxmind' ? 'MaxMind' : 'IPInfo', + '', + ]); + }); + }); + + test('deletes a database', async () => { + const { actions } = testBed; + const databaseIndexToDelete = 0; + const databaseName = databases[databaseIndexToDelete].name; + httpRequestsMockHelpers.setDeleteDatabasesResponse(databaseName, {}); + + await actions.clickDeleteDatabaseButton(databaseIndexToDelete); + + await actions.confirmDeletingDatabase(); + + expect(httpSetup.delete).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/databases/${databaseName.toLowerCase()}`, + expect.anything() + ); + }); + }); + + describe('Creates a database', () => { + it('creates a MaxMind database when none with the same name exists', async () => { + const { actions, exists } = testBed; + const databaseName = 'GeoIP2-ISP'; + const maxmind = '123456'; + httpRequestsMockHelpers.setCreateDatabasesResponse({ + name: databaseName, + id: databaseName.toLowerCase(), + }); + + await actions.clickAddDatabaseButton(); + + expect(exists('addGeoipDatabaseForm')).toBe(true); + + await actions.fillOutDatabaseValues('maxmind', databaseName, maxmind); + + await actions.confirmAddingDatabase(); + + expect(httpSetup.post).toHaveBeenLastCalledWith(`${API_BASE_PATH}/databases`, { + asSystemRequest: undefined, + body: '{"databaseType":"maxmind","databaseName":"GeoIP2-ISP","maxmind":"123456"}', + query: undefined, + version: undefined, + }); + }); + + it('creates an IPInfo database when none with the same name exists', async () => { + const { actions, exists } = testBed; + const databaseName = 'ASN'; + httpRequestsMockHelpers.setCreateDatabasesResponse({ + name: databaseName, + id: databaseName.toLowerCase(), + }); + + await actions.clickAddDatabaseButton(); + + expect(exists('addGeoipDatabaseForm')).toBe(true); + + await actions.fillOutDatabaseValues('ipinfo', databaseName); + + await actions.confirmAddingDatabase(); + + expect(httpSetup.post).toHaveBeenLastCalledWith(`${API_BASE_PATH}/databases`, { + asSystemRequest: undefined, + body: '{"databaseType":"ipinfo","databaseName":"ASN","maxmind":""}', + query: undefined, + version: undefined, + }); + }); + }); + + describe('No databases', () => { + test('displays an empty prompt', async () => { + httpRequestsMockHelpers.setLoadDatabasesResponse([]); + + await act(async () => { + testBed = await setup(httpSetup); + }); + const { exists, component } = testBed; + component.update(); + + expect(exists('geoipEmptyListPrompt')).toBe(true); + }); + }); + + describe('Error handling', () => { + beforeEach(async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; + + httpRequestsMockHelpers.setLoadDatabasesResponse(undefined, error); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + }); + + test('displays an error callout', async () => { + const { exists } = testBed; + + expect(exists('geoipListLoadingError')).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/common/types.ts b/x-pack/plugins/ingest_pipelines/common/types.ts index c526facdedab8..4c68b443fb8fb 100644 --- a/x-pack/plugins/ingest_pipelines/common/types.ts +++ b/x-pack/plugins/ingest_pipelines/common/types.ts @@ -28,16 +28,15 @@ export interface Pipeline { deprecated?: boolean; } -export interface PipelinesByName { - [key: string]: { - description: string; - version?: number; - processors: Processor[]; - on_failure?: Processor[]; - }; -} - export enum FieldCopyAction { Copy = 'copy', Rename = 'rename', } + +export type DatabaseType = 'maxmind' | 'ipinfo' | 'web' | 'local' | 'unknown'; + +export interface GeoipDatabase { + name: string; + id: string; + type: DatabaseType; +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index 6b47ed277673e..045db4511e181 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -27,20 +27,27 @@ import { PipelinesEdit, PipelinesClone, PipelinesCreateFromCsv, + ManageProcessors, } from './sections'; import { ROUTES } from './services/navigation'; -export const AppWithoutRouter = () => ( - <Routes> - <Route exact path={ROUTES.list} component={PipelinesList} /> - <Route exact path={ROUTES.clone} component={PipelinesClone} /> - <Route exact path={ROUTES.create} component={PipelinesCreate} /> - <Route exact path={ROUTES.edit} component={PipelinesEdit} /> - <Route exact path={ROUTES.createFromCsv} component={PipelinesCreateFromCsv} /> - {/* Catch all */} - <Route component={PipelinesList} /> - </Routes> -); +export const AppWithoutRouter = () => { + const { services } = useKibana(); + return ( + <Routes> + <Route exact path={ROUTES.list} component={PipelinesList} /> + <Route exact path={ROUTES.clone} component={PipelinesClone} /> + <Route exact path={ROUTES.create} component={PipelinesCreate} /> + <Route exact path={ROUTES.edit} component={PipelinesEdit} /> + <Route exact path={ROUTES.createFromCsv} component={PipelinesCreateFromCsv} /> + {services.config.enableManageProcessors && ( + <Route exact path={ROUTES.manageProcessors} component={ManageProcessors} /> + )} + {/* Catch all */} + <Route component={PipelinesList} /> + </Routes> + ); +}; export const App: FunctionComponent = () => { const { apiError } = useAuthorizationContext(); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts index 2e4dc65f32314..b55337f088887 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts @@ -25,6 +25,7 @@ export { Fingerprint } from './fingerprint'; export { Foreach } from './foreach'; export { GeoGrid } from './geogrid'; export { GeoIP } from './geoip'; +export { IpLocation } from './ip_location'; export { Grok } from './grok'; export { Gsub } from './gsub'; export { HtmlStrip } from './html_strip'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/ip_location.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/ip_location.tsx new file mode 100644 index 0000000000000..d1b8fbd7ea513 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/ip_location.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCode } from '@elastic/eui'; +import { groupBy, map } from 'lodash'; + +import { + FIELD_TYPES, + UseField, + ToggleField, + ComboBoxField, +} from '../../../../../../shared_imports'; + +import { useKibana } from '../../../../../../shared_imports'; +import { FieldNameField } from './common_fields/field_name_field'; +import { IgnoreMissingField } from './common_fields/ignore_missing_field'; +import { FieldsConfig, from, to } from './shared'; +import { TargetField } from './common_fields/target_field'; +import { PropertiesField } from './common_fields/properties_field'; +import type { GeoipDatabase } from '../../../../../../../common/types'; +import { getTypeLabel } from '../../../../../sections/manage_processors/constants'; + +const fieldsConfig: FieldsConfig = { + /* Optional field config */ + database_file: { + type: FIELD_TYPES.COMBO_BOX, + deserializer: to.arrayOfStrings, + serializer: (v: string[]) => (v.length ? v[0] : undefined), + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.ipLocationForm.databaseFileLabel', { + defaultMessage: 'Database file (optional)', + }), + helpText: ( + <FormattedMessage + id="xpack.ingestPipelines.pipelineEditor.ipLocationForm.databaseFileHelpText" + defaultMessage="GeoIP2 database file in the {ingestGeoIP} configuration directory. Defaults to {databaseFile}." + values={{ + databaseFile: <EuiCode>{'GeoLite2-City.mmdb'}</EuiCode>, + ingestGeoIP: <EuiCode>{'ingest-geoip'}</EuiCode>, + }} + /> + ), + }, + + first_only: { + type: FIELD_TYPES.TOGGLE, + defaultValue: true, + deserializer: to.booleanOrUndef, + serializer: from.undefinedIfValue(true), + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.ipLocationForm.firstOnlyFieldLabel', + { + defaultMessage: 'First only', + } + ), + helpText: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.ipLocationForm.firstOnlyFieldHelpText', + { + defaultMessage: 'Use the first matching geo data, even if the field contains an array.', + } + ), + }, +}; + +export const IpLocation: FunctionComponent = () => { + const { services } = useKibana(); + const { data, isLoading } = services.api.useLoadDatabases(); + + const dataAsOptions = (data || []).map((item) => ({ + id: item.id, + type: item.type, + label: item.name, + })); + const optionsByGroup = groupBy(dataAsOptions, 'type'); + const groupedOptions = map(optionsByGroup, (items, groupName) => ({ + label: getTypeLabel(groupName as GeoipDatabase['type']), + options: map(items, (item) => item), + })); + + return ( + <> + <FieldNameField + helpText={i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.ipLocationForm.fieldNameHelpText', + { defaultMessage: 'Field containing an IP address for the geographical lookup.' } + )} + /> + + <TargetField + helpText={i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.ipLocationForm.targetFieldHelpText', + { + defaultMessage: 'Field used to contain geo data properties.', + } + )} + /> + + <UseField + component={ComboBoxField} + config={fieldsConfig.database_file} + path="fields.database_file" + euiFieldProps={{ + isLoading, + noSuggestions: false, + singleSelection: { asPlainText: true }, + options: groupedOptions, + }} + /> + + <PropertiesField + helpText={i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.ipLocationForm.propertiesFieldHelpText', + { + defaultMessage: + 'Properties added to the target field. Valid properties depend on the database file used.', + } + )} + /> + + <UseField component={ToggleField} config={fieldsConfig.first_only} path="fields.first_only" /> + + <IgnoreMissingField /> + </> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index 5d672deb739d3..6618e1bd9b352 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -32,6 +32,7 @@ import { Foreach, GeoGrid, GeoIP, + IpLocation, Grok, Gsub, HtmlStrip, @@ -477,6 +478,24 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, }), }, + ip_location: { + category: processorCategories.DATA_ENRICHMENT, + FieldsComponent: IpLocation, + docLinkPath: '/geoip-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.ipLocation', { + defaultMessage: 'IP Location', + }), + typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.ipLocation', { + defaultMessage: 'Adds geo data based on an IP address.', + }), + getDefaultDescription: ({ field }) => + i18n.translate('xpack.ingestPipelines.processors.defaultDescription.ipLocation', { + defaultMessage: 'Adds geo data to documents based on the value of "{field}"', + values: { + field, + }, + }), + }, grok: { category: processorCategories.DATA_TRANSFORMATION, FieldsComponent: Grok, diff --git a/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts b/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts index 3c415bf9e0682..03aa734800ff6 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts @@ -13,3 +13,4 @@ export const UIM_PIPELINE_UPDATE = 'pipeline_update'; export const UIM_PIPELINE_DELETE = 'pipeline_delete'; export const UIM_PIPELINE_DELETE_MANY = 'pipeline_delete_many'; export const UIM_PIPELINE_SIMULATE = 'pipeline_simulate'; +export const UIM_MANAGE_PROCESSORS = 'manage_processes'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index 6ec215db8b043..9bc3ba7fe27ad 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -18,7 +18,7 @@ import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import { KibanaContextProvider, KibanaRenderContextProvider } from '../shared_imports'; -import { ILicense } from '../types'; +import type { Config, ILicense } from '../types'; import { API_BASE_PATH } from '../../common/constants'; @@ -50,6 +50,7 @@ export interface AppServices { consolePlugin?: ConsolePluginStart; overlays: OverlayStart; http: HttpStart; + config: Config; } type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>; @@ -66,7 +67,7 @@ export const renderApp = ( render( <KibanaRenderContextProvider {...coreServices}> <AuthorizationProvider - privilegesEndpoint={`${API_BASE_PATH}/privileges`} + privilegesEndpoint={`${API_BASE_PATH}/privileges/ingest_pipelines`} httpClient={coreServices.http} > <KibanaContextProvider services={services}> diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index 4b6ca4f35cd3f..c4382e73720d7 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -8,7 +8,7 @@ import { CoreSetup } from '@kbn/core/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { StartDependencies, ILicense } from '../types'; +import type { StartDependencies, ILicense, Config } from '../types'; import { documentationService, uiMetricService, @@ -20,13 +20,14 @@ import { renderApp } from '.'; export interface AppParams extends ManagementAppMountParams { license: ILicense | null; + config: Config; } export async function mountManagementSection( { http, getStartServices, notifications }: CoreSetup<StartDependencies>, params: AppParams ) { - const { element, setBreadcrumbs, history, license } = params; + const { element, setBreadcrumbs, history, license, config } = params; const [coreStart, depsStart] = await getStartServices(); const { docLinks, application, executionContext, overlays } = coreStart; @@ -51,6 +52,7 @@ export async function mountManagementSection( consolePlugin: depsStart.console, overlays, http, + config, }; return renderApp(element, services, { ...coreStart, http }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts index bd3ab41936b29..f299c9ec0db74 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts @@ -14,3 +14,5 @@ export { PipelinesEdit } from './pipelines_edit'; export { PipelinesClone } from './pipelines_clone'; export { PipelinesCreateFromCsv } from './pipelines_create_from_csv'; + +export { ManageProcessors } from './manage_processors'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/add_database_modal.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/add_database_modal.tsx new file mode 100644 index 0000000000000..6289fe3953f3e --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/add_database_modal.tsx @@ -0,0 +1,280 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; +import React, { useMemo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import type { GeoipDatabase } from '../../../../common/types'; +import { useKibana } from '../../../shared_imports'; +import { + ADD_DATABASE_MODAL_TITLE_ID, + ADD_DATABASE_MODAL_FORM_ID, + DATABASE_TYPE_OPTIONS, + GEOIP_NAME_OPTIONS, + IPINFO_NAME_OPTIONS, + getAddDatabaseSuccessMessage, + addDatabaseErrorTitle, +} from './constants'; + +export const AddDatabaseModal = ({ + closeModal, + reloadDatabases, + databases, +}: { + closeModal: () => void; + reloadDatabases: () => void; + databases: GeoipDatabase[]; +}) => { + const [databaseType, setDatabaseType] = useState<string | undefined>(undefined); + const [maxmind, setMaxmind] = useState(''); + const [databaseName, setDatabaseName] = useState(''); + const [nameExistsError, setNameExistsError] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const existingDatabaseNames = useMemo( + () => databases.map((database) => database.name), + [databases] + ); + const { services } = useKibana(); + const onDatabaseNameChange = (value: string) => { + setDatabaseName(value); + setNameExistsError(existingDatabaseNames.includes(value)); + }; + const isFormValid = (): boolean => { + if (!databaseType || nameExistsError) { + return false; + } + if (databaseType === 'maxmind') { + return Boolean(maxmind) && Boolean(databaseName); + } + return Boolean(databaseName); + }; + const onDatabaseTypeChange = (value: string) => { + setDatabaseType(value); + }; + const onAddDatabase = async (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + if (!isFormValid()) { + return; + } + setIsLoading(true); + try { + const { error } = await services.api.createDatabase({ + databaseType: databaseType!, + databaseName, + maxmind, + }); + setIsLoading(false); + if (error) { + services.notifications.toasts.addError(error, { + title: addDatabaseErrorTitle, + }); + } else { + services.notifications.toasts.addSuccess(getAddDatabaseSuccessMessage(databaseName)); + await reloadDatabases(); + closeModal(); + } + } catch (e) { + setIsLoading(false); + services.notifications.toasts.addError(e, { + title: addDatabaseErrorTitle, + }); + } + }; + + return ( + <EuiModal + css={css` + width: 500px; + `} + aria-labelledby={ADD_DATABASE_MODAL_TITLE_ID} + onClose={closeModal} + initialFocus={'[data-test-subj="databaseTypeSelect"]'} + > + <EuiModalHeader> + <EuiModalHeaderTitle id={ADD_DATABASE_MODAL_TITLE_ID}> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addDatabaseModalTitle" + defaultMessage="Add database" + /> + </EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <EuiForm + fullWidth={true} + id={ADD_DATABASE_MODAL_FORM_ID} + component="form" + onSubmit={(event) => onAddDatabase(event)} + data-test-subj="addGeoipDatabaseForm" + > + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addDatabaseForm.databaseTypeSelectLabel" + defaultMessage="Type" + /> + } + helpText={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addDatabaseForm.databaseTypeSelectHelpText" + defaultMessage="Select which provider you want to use" + /> + } + > + <EuiSelect + options={DATABASE_TYPE_OPTIONS} + hasNoInitialSelection={true} + value={databaseType} + onChange={(e) => onDatabaseTypeChange(e.target.value)} + data-test-subj="databaseTypeSelect" + /> + </EuiFormRow> + {databaseType === 'maxmind' && ( + <> + <EuiSpacer /> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.licenseCalloutTitle" + defaultMessage="Add your MaxMind license key to keystore" + /> + } + iconType="iInCircle" + > + <p> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.licenseCalloutText" + defaultMessage="In order to grant access to your MaxMind account, you must add the license key to the keystore." + /> + </p> + </EuiCallOut> + <EuiSpacer /> + </> + )} + {databaseType === 'ipinfo' && ( + <> + <EuiSpacer /> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.licenseCalloutTitle" + defaultMessage="Add your IP Info license token to keystore" + /> + } + iconType="iInCircle" + > + <p> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.licenseCalloutText" + defaultMessage="In order to grant access to your IP Info account, you must add the license token to the keystore." + /> + </p> + </EuiCallOut> + <EuiSpacer /> + </> + )} + + {databaseType === 'maxmind' && ( + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addDatabaseForm.maxMindInputLabel" + defaultMessage="MaxMind Account ID" + /> + } + > + <EuiFieldText + value={maxmind} + onChange={(e) => setMaxmind(e.target.value)} + data-test-subj="maxmindField" + /> + </EuiFormRow> + )} + {databaseType && ( + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addDatabaseForm.databaseNameSelectLabel" + defaultMessage="Database name" + /> + } + > + <EuiSelect + options={databaseType === 'maxmind' ? GEOIP_NAME_OPTIONS : IPINFO_NAME_OPTIONS} + hasNoInitialSelection={true} + value={databaseName} + onChange={(e) => onDatabaseNameChange(e.target.value)} + data-test-subj="databaseNameSelect" + /> + </EuiFormRow> + )} + </EuiForm> + + {nameExistsError && ( + <> + <EuiSpacer /> + <EuiCallOut + color="danger" + title={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.nameExistsErrorTitle" + defaultMessage="Database already exists" + /> + } + iconType="warning" + > + <p> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.nameExistsErrorText" + defaultMessage="Database cannot be added multiple times." + /> + </p> + </EuiCallOut> + </> + )} + </EuiModalBody> + + <EuiModalFooter> + <EuiButtonEmpty onClick={closeModal}> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addModalCancelButtonLabel" + defaultMessage="Cancel" + /> + </EuiButtonEmpty> + + <EuiButton + fill + type="submit" + form={ADD_DATABASE_MODAL_FORM_ID} + disabled={isLoading || !isFormValid()} + data-test-subj="addGeoipDatabaseSubmit" + > + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addModalConfirmButtonLabel" + defaultMessage="Add database" + /> + </EuiButton> + </EuiModalFooter> + </EuiModal> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/constants.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/constants.ts new file mode 100644 index 0000000000000..799c3a8c29b40 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/constants.ts @@ -0,0 +1,176 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { GeoipDatabase } from '../../../../common/types'; + +export const ADD_DATABASE_MODAL_TITLE_ID = 'manageProcessorsAddGeoipDatabase'; +export const ADD_DATABASE_MODAL_FORM_ID = 'manageProcessorsAddGeoipDatabaseForm'; +export const DATABASE_TYPE_OPTIONS = [ + { + value: 'maxmind', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.maxmindDatabaseType', { + defaultMessage: 'MaxMind', + }), + }, + { + value: 'ipinfo', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.ipinfoDatabaseType', { + defaultMessage: 'IPInfo', + }), + }, +]; +export const GEOIP_NAME_OPTIONS = [ + { + value: 'GeoIP2-Anonymous-IP', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.anonymousIPDatabaseName', { + defaultMessage: 'GeoIP2 Anonymous IP', + }), + }, + { + value: 'GeoIP2-City', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.cityDatabaseName', { + defaultMessage: 'GeoIP2 City', + }), + }, + { + value: 'GeoIP2-Connection-Type', + text: i18n.translate( + 'xpack.ingestPipelines.manageProcessors.geoip.connectionTypeDatabaseName', + { + defaultMessage: 'GeoIP2 Connection Type', + } + ), + }, + { + value: 'GeoIP2-Country', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.countryDatabaseName', { + defaultMessage: 'GeoIP2 Country', + }), + }, + { + value: 'GeoIP2-Domain', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.domainDatabaseName', { + defaultMessage: 'GeoIP2 Domain', + }), + }, + { + value: 'GeoIP2-Enterprise', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.enterpriseDatabaseName', { + defaultMessage: 'GeoIP2 Enterprise', + }), + }, + { + value: 'GeoIP2-ISP', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.ispDatabaseName', { + defaultMessage: 'GeoIP2 ISP', + }), + }, +]; +export const IPINFO_NAME_OPTIONS = [ + { + value: 'asn', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.ipinfo.freeAsnDatabaseName', { + defaultMessage: 'Free IP to ASN', + }), + }, + { + value: 'country', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.ipinfo.freeCountryDatabaseName', { + defaultMessage: 'Free IP to Country', + }), + }, + { + value: 'standard_asn', + text: i18n.translate('xpack.ingestPipelines.manageProcessors.ipinfo.asnDatabaseName', { + defaultMessage: 'ASN', + }), + }, + { + value: 'standard_location', + text: i18n.translate( + 'xpack.ingestPipelines.manageProcessors.ipinfo.ipGeolocationDatabaseName', + { + defaultMessage: 'IP Geolocation', + } + ), + }, + { + value: 'standard_privacy', + text: i18n.translate( + 'xpack.ingestPipelines.manageProcessors.ipinfo.privacyDetectionDatabaseName', + { + defaultMessage: 'Privacy Detection', + } + ), + }, +]; + +export const getAddDatabaseSuccessMessage = (databaseName: string): string => { + return i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.addDatabaseSuccessMessage', { + defaultMessage: 'Added database {databaseName}', + values: { databaseName }, + }); +}; + +export const addDatabaseErrorTitle = i18n.translate( + 'xpack.ingestPipelines.manageProcessors.geoip.addDatabaseErrorTitle', + { + defaultMessage: 'Error adding database', + } +); + +export const DELETE_DATABASE_MODAL_TITLE_ID = 'manageProcessorsDeleteGeoipDatabase'; +export const DELETE_DATABASE_MODAL_FORM_ID = 'manageProcessorsDeleteGeoipDatabaseForm'; + +export const getDeleteDatabaseSuccessMessage = (databaseName: string): string => { + return i18n.translate( + 'xpack.ingestPipelines.manageProcessors.geoip.deleteDatabaseSuccessMessage', + { + defaultMessage: 'Deleted database {databaseName}', + values: { databaseName }, + } + ); +}; + +export const deleteDatabaseErrorTitle = i18n.translate( + 'xpack.ingestPipelines.manageProcessors.geoip.deleteDatabaseErrorTitle', + { + defaultMessage: 'Error deleting database', + } +); + +export const getTypeLabel = (type: GeoipDatabase['type']): string => { + switch (type) { + case 'maxmind': { + return i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.typeMaxmindLabel', { + defaultMessage: 'MaxMind', + }); + } + case 'ipinfo': { + return i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.typeIpinfoLabel', { + defaultMessage: 'IPInfo', + }); + } + case 'web': { + return i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.webLabel', { + defaultMessage: 'Web', + }); + } + case 'local': { + return i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.localLabel', { + defaultMessage: 'Local', + }); + } + case 'unknown': + default: { + return i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.typeUnknownLabel', { + defaultMessage: 'Unknown', + }); + } + } +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/delete_database_modal.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/delete_database_modal.tsx new file mode 100644 index 0000000000000..711fab34984a5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/delete_database_modal.tsx @@ -0,0 +1,135 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useState } from 'react'; +import type { GeoipDatabase } from '../../../../common/types'; +import { useKibana } from '../../../shared_imports'; +import { + DELETE_DATABASE_MODAL_FORM_ID, + DELETE_DATABASE_MODAL_TITLE_ID, + deleteDatabaseErrorTitle, + getDeleteDatabaseSuccessMessage, +} from './constants'; + +export const DeleteDatabaseModal = ({ + closeModal, + database, + reloadDatabases, +}: { + closeModal: () => void; + database: GeoipDatabase; + reloadDatabases: () => void; +}) => { + const [confirmation, setConfirmation] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const isValid = confirmation === 'delete'; + const { services } = useKibana(); + const onDeleteDatabase = async (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + if (!isValid) { + return; + } + setIsLoading(true); + try { + const { error } = await services.api.deleteDatabase(database.id); + setIsLoading(false); + if (error) { + services.notifications.toasts.addError(error, { + title: deleteDatabaseErrorTitle, + }); + } else { + services.notifications.toasts.addSuccess(getDeleteDatabaseSuccessMessage(database.name)); + await reloadDatabases(); + closeModal(); + } + } catch (e) { + setIsLoading(false); + services.notifications.toasts.addError(e, { + title: deleteDatabaseErrorTitle, + }); + } + }; + return ( + <EuiModal + aria-labelledby={DELETE_DATABASE_MODAL_TITLE_ID} + onClose={closeModal} + initialFocus="[name=confirmation]" + > + <EuiModalHeader> + <EuiModalHeaderTitle id={DELETE_DATABASE_MODAL_TITLE_ID}> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.deleteDatabaseModalTitle" + defaultMessage="Delete {database}" + values={{ + database: database.name, + }} + /> + </EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <EuiForm + id={DELETE_DATABASE_MODAL_FORM_ID} + component="form" + onSubmit={(event) => onDeleteDatabase(event)} + > + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.deleteDatabaseForm.confirmationLabel" + defaultMessage={'Please type "delete" to confirm.'} + /> + } + > + <EuiFieldText + name="confirmation" + value={confirmation} + onChange={(e) => setConfirmation(e.target.value)} + data-test-subj="geoipDatabaseConfirmation" + /> + </EuiFormRow> + </EuiForm> + </EuiModalBody> + + <EuiModalFooter> + <EuiButtonEmpty onClick={closeModal}> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.deleteModalCancelButtonLabel" + defaultMessage="Cancel" + /> + </EuiButtonEmpty> + + <EuiButton + fill + type="submit" + form={DELETE_DATABASE_MODAL_FORM_ID} + disabled={isLoading || !isValid} + color="danger" + data-test-subj="deleteGeoipDatabaseSubmit" + > + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.deleteModalConfirmButtonLabel" + defaultMessage="Delete" + /> + </EuiButton> + </EuiModalFooter> + </EuiModal> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/empty_list.tsx new file mode 100644 index 0000000000000..d5e908b155feb --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/empty_list.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiPageTemplate } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +export const EmptyList = ({ addDatabaseButton }: { addDatabaseButton: JSX.Element }) => { + return ( + <EuiPageTemplate.EmptyPrompt + iconType="database" + iconColor="default" + title={ + <h2 data-test-subj="geoipEmptyListPrompt"> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.emptyPromptTitle" + defaultMessage="Add your first database for IP Location processor" + /> + </h2> + } + body={ + <p> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.emptyPromptDescription" + defaultMessage="Use a custom database when setting up IP Location processor." + /> + </p> + } + actions={addDatabaseButton} + /> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/geoip_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/geoip_list.tsx new file mode 100644 index 0000000000000..e09ac4e6e2c4d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/geoip_list.tsx @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiInMemoryTableProps, + EuiPageTemplate, + EuiSpacer, + EuiTitle, + EuiButtonIcon, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; + +import { IPINFO_NAME_OPTIONS } from './constants'; +import type { GeoipDatabase } from '../../../../common/types'; +import { SectionLoading, useKibana } from '../../../shared_imports'; +import { getTypeLabel } from './constants'; +import { EmptyList } from './empty_list'; +import { AddDatabaseModal } from './add_database_modal'; +import { DeleteDatabaseModal } from './delete_database_modal'; +import { getErrorMessage } from './get_error_message'; + +export const GeoipList: React.FunctionComponent = () => { + const { services } = useKibana(); + const { data, isLoading, error, resendRequest } = services.api.useLoadDatabases(); + const [showModal, setShowModal] = useState<'add' | 'delete' | null>(null); + const [databaseToDelete, setDatabaseToDelete] = useState<GeoipDatabase | null>(null); + const onDatabaseDelete = (item: GeoipDatabase) => { + setDatabaseToDelete(item); + setShowModal('delete'); + }; + let content: JSX.Element; + const addDatabaseButton = ( + <EuiButton + fill + iconType="plusInCircle" + onClick={() => { + setShowModal('add'); + }} + data-test-subj="addGeoipDatabaseButton" + > + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.addDatabaseButtonLabel" + defaultMessage="Add database" + /> + </EuiButton> + ); + const tableProps: EuiInMemoryTableProps<GeoipDatabase> = { + 'data-test-subj': 'geoipDatabaseList', + rowProps: () => ({ + 'data-test-subj': 'geoipDatabaseListRow', + }), + columns: [ + { + field: 'name', + name: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.nameColumnTitle', { + defaultMessage: 'Database name', + }), + sortable: true, + render: (name: string, row) => { + if (row.type === 'ipinfo') { + // find the name in the options to get the translated value + const option = IPINFO_NAME_OPTIONS.find((opt) => opt.value === name); + return option?.text ?? name; + } + + return name; + }, + }, + { + field: 'type', + name: i18n.translate('xpack.ingestPipelines.manageProcessors.geoip.list.typeColumnTitle', { + defaultMessage: 'Type', + }), + sortable: true, + render: (type: GeoipDatabase['type']) => { + return getTypeLabel(type); + }, + }, + { + name: 'Actions', + align: 'right', + render: (item: GeoipDatabase) => { + // Local and web databases are read only and cannot be deleted through UI + if (['web', 'local'].includes(item.type)) { + return; + } + + return ( + <EuiButtonIcon + name="Delete" + aria-label={i18n.translate( + 'xpack.ingestPipelines.manageProcessors.geoip.list.actionIconLabel', + { + defaultMessage: 'Delete this database', + } + )} + iconType="trash" + color="danger" + onClick={() => onDatabaseDelete(item)} + data-test-subj="deleteGeoipDatabaseButton" + /> + ); + }, + }, + ], + items: data ?? [], + }; + if (error) { + content = ( + <EuiPageTemplate.EmptyPrompt + color="danger" + iconType="warning" + title={ + <h2 data-test-subj="geoipListLoadingError"> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.list.loadErrorTitle" + defaultMessage="Unable to load geoIP databases" + /> + </h2> + } + body={<p>{getErrorMessage(error)}</p>} + actions={ + <EuiButton onClick={resendRequest} iconType="refresh" color="danger"> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.list.geoipListReloadButton" + defaultMessage="Try again" + /> + </EuiButton> + } + /> + ); + } else if (isLoading && !data) { + content = ( + <SectionLoading data-test-subj="sectionLoading"> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.list.loadingMessage" + defaultMessage="Loading geoIP databases..." + /> + </SectionLoading> + ); + } else if (data && data.length === 0) { + content = <EmptyList addDatabaseButton={addDatabaseButton} />; + } else { + content = ( + <> + <EuiFlexGroup> + <EuiFlexItem> + <EuiTitle> + <h2> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.geoip.tableTitle" + defaultMessage="GeoIP" + /> + </h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}>{addDatabaseButton}</EuiFlexItem> + </EuiFlexGroup> + + <EuiSpacer size="l" /> + <EuiInMemoryTable + css={css` + height: 100%; + `} + {...tableProps} + /> + </> + ); + } + return ( + <> + {content} + {showModal === 'add' && ( + <AddDatabaseModal + closeModal={() => setShowModal(null)} + reloadDatabases={resendRequest} + databases={data!} + /> + )} + {showModal === 'delete' && databaseToDelete && ( + <DeleteDatabaseModal + database={databaseToDelete} + reloadDatabases={resendRequest} + closeModal={() => setShowModal(null)} + /> + )} + </> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/get_error_message.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/get_error_message.tsx new file mode 100644 index 0000000000000..09767f328da50 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/get_error_message.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiCode } from '@elastic/eui'; +import { ResponseErrorBody } from '@kbn/core-http-browser'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const getErrorMessage = (error: ResponseErrorBody) => { + if (error.statusCode === 403) { + return ( + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.deniedPrivilegeDescription" + defaultMessage="To manage geoIP databases, you must have the {manage} cluster privilege." + values={{ + manage: <EuiCode>manage</EuiCode>, + }} + /> + ); + } + + return error.message; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/index.ts new file mode 100644 index 0000000000000..517fe284874f8 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ManageProcessors } from './manage_processors'; +export { useCheckManageProcessorsPrivileges } from './use_check_manage_processors_privileges'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/manage_processors.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/manage_processors.tsx new file mode 100644 index 0000000000000..d721441856b15 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/manage_processors.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useKibana } from '../../../shared_imports'; +import { UIM_MANAGE_PROCESSORS } from '../../constants'; +import { GeoipList } from './geoip_list'; + +export const ManageProcessors: React.FunctionComponent = () => { + const { services } = useKibana(); + // Track component loaded + useEffect(() => { + services.metric.trackUiMetric(UIM_MANAGE_PROCESSORS); + services.breadcrumbs.setBreadcrumbs('manage_processors'); + }, [services.metric, services.breadcrumbs]); + + return ( + <> + <EuiPageHeader + bottomBorder + pageTitle={ + <span data-test-subj="manageProcessorsTitle"> + <FormattedMessage + id="xpack.ingestPipelines.manageProcessors.pageTitle" + defaultMessage="Manage Processors" + /> + </span> + } + /> + + <EuiSpacer size="l" /> + + <GeoipList /> + </> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/use_check_manage_processors_privileges.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/use_check_manage_processors_privileges.ts new file mode 100644 index 0000000000000..c1afa6dc94209 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/manage_processors/use_check_manage_processors_privileges.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useKibana } from '../../../shared_imports'; + +export const useCheckManageProcessorsPrivileges = () => { + const { services } = useKibana(); + const { isLoading, data: privilegesData } = services.api.useLoadManageProcessorsPrivileges(); + const hasPrivileges = privilegesData?.hasAllPrivileges; + return isLoading ? false : !!hasPrivileges; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx index 886bfcf8b9029..55456ee54e8c9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx @@ -26,7 +26,14 @@ import { import { Pipeline } from '../../../../common/types'; import { useKibana, SectionLoading } from '../../../shared_imports'; import { UIM_PIPELINES_LIST_LOAD } from '../../constants'; -import { getEditPath, getClonePath } from '../../services/navigation'; +import { + getEditPath, + getClonePath, + getCreateFromCsvPath, + getCreatePath, + getManageProcessorsPath, +} from '../../services/navigation'; +import { useCheckManageProcessorsPrivileges } from '../manage_processors'; import { EmptyList } from './empty_list'; import { PipelineTable } from './table'; @@ -54,6 +61,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ const { data, isLoading, error, resendRequest } = services.api.useLoadPipelines(); + const hasManageProcessorsPrivileges = useCheckManageProcessorsPrivileges(); // Track component loaded useEffect(() => { services.metric.trackUiMetric(UIM_PIPELINES_LIST_LOAD); @@ -142,7 +150,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ name: i18n.translate('xpack.ingestPipelines.list.table.createPipelineButtonLabel', { defaultMessage: 'New pipeline', }), - ...reactRouterNavigate(history, '/create'), + ...reactRouterNavigate(history, getCreatePath()), 'data-test-subj': `createNewPipeline`, }, /** @@ -152,10 +160,71 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ name: i18n.translate('xpack.ingestPipelines.list.table.createPipelineFromCsvButtonLabel', { defaultMessage: 'New pipeline from CSV', }), - ...reactRouterNavigate(history, '/csv_create'), + ...reactRouterNavigate(history, getCreateFromCsvPath()), 'data-test-subj': `createPipelineFromCsv`, }, ]; + const titleActionButtons = [ + <EuiPopover + key="createPipelinePopover" + isOpen={showPopover} + closePopover={() => setShowPopover(false)} + button={ + <EuiButton + fill + iconSide="right" + iconType="arrowDown" + data-test-subj="createPipelineDropdown" + key="createPipelineDropdown" + onClick={() => setShowPopover((previousBool) => !previousBool)} + > + {i18n.translate('xpack.ingestPipelines.list.table.createPipelineDropdownLabel', { + defaultMessage: 'Create pipeline', + })} + </EuiButton> + } + panelPaddingSize="none" + repositionOnScroll + > + <EuiContextMenu + initialPanelId={0} + data-test-subj="autoFollowPatternActionContextMenu" + panels={[ + { + id: 0, + items: createMenuItems, + }, + ]} + /> + </EuiPopover>, + ]; + if (services.config.enableManageProcessors && hasManageProcessorsPrivileges) { + titleActionButtons.push( + <EuiButtonEmpty + iconType="wrench" + data-test-subj="manageProcessorsLink" + {...reactRouterNavigate(history, getManageProcessorsPath())} + > + <FormattedMessage + id="xpack.ingestPipelines.list.manageProcessorsLinkText" + defaultMessage="Manage processors" + /> + </EuiButtonEmpty> + ); + } + titleActionButtons.push( + <EuiButtonEmpty + href={services.documentation.getIngestNodeUrl()} + target="_blank" + iconType="help" + data-test-subj="documentationLink" + > + <FormattedMessage + id="xpack.ingestPipelines.list.pipelinesDocsLinkText" + defaultMessage="Documentation" + /> + </EuiButtonEmpty> + ); const renderFlyout = (): React.ReactNode => { if (!showFlyout) { @@ -199,51 +268,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ defaultMessage="Use ingest pipelines to remove or transform fields, extract values from text, and enrich your data before indexing into Elasticsearch." /> } - rightSideItems={[ - <EuiPopover - key="createPipelinePopover" - isOpen={showPopover} - closePopover={() => setShowPopover(false)} - button={ - <EuiButton - fill - iconSide="right" - iconType="arrowDown" - data-test-subj="createPipelineDropdown" - key="createPipelineDropdown" - onClick={() => setShowPopover((previousBool) => !previousBool)} - > - {i18n.translate('xpack.ingestPipelines.list.table.createPipelineDropdownLabel', { - defaultMessage: 'Create pipeline', - })} - </EuiButton> - } - panelPaddingSize="none" - repositionOnScroll - > - <EuiContextMenu - initialPanelId={0} - data-test-subj="autoFollowPatternActionContextMenu" - panels={[ - { - id: 0, - items: createMenuItems, - }, - ]} - /> - </EuiPopover>, - <EuiButtonEmpty - href={services.documentation.getIngestNodeUrl()} - target="_blank" - iconType="help" - data-test-subj="documentationLink" - > - <FormattedMessage - id="xpack.ingestPipelines.list.pipelinesDocsLinkText" - defaultMessage="Documentation" - /> - </EuiButtonEmpty>, - ]} + rightSideItems={titleActionButtons} /> <EuiSpacer size="l" /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts index f687c80351075..e32245e325b15 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { HttpSetup } from '@kbn/core/public'; +import { HttpSetup, ResponseErrorBody } from '@kbn/core/public'; -import { FieldCopyAction, Pipeline } from '../../../common/types'; +import type { FieldCopyAction, GeoipDatabase, Pipeline } from '../../../common/types'; import { API_BASE_PATH } from '../../../common/constants'; import { UseRequestConfig, @@ -140,6 +140,39 @@ export class ApiService { }); return result; } + + public useLoadDatabases() { + return this.useRequest<GeoipDatabase[], ResponseErrorBody>({ + path: `${API_BASE_PATH}/databases`, + method: 'get', + }); + } + + public async createDatabase(database: { + databaseType: string; + maxmind?: string; + databaseName: string; + }) { + return this.sendRequest({ + path: `${API_BASE_PATH}/databases`, + method: 'post', + body: JSON.stringify(database), + }); + } + + public async deleteDatabase(id: string) { + return this.sendRequest({ + path: `${API_BASE_PATH}/databases/${id}`, + method: 'delete', + }); + } + + public useLoadManageProcessorsPrivileges() { + return this.useRequest<{ hasAllPrivileges: boolean }>({ + path: `${API_BASE_PATH}/privileges/manage_processors`, + method: 'get', + }); + } } export const apiService = new ApiService(); diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts index f09b1325f7982..e8b010917cfae 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts @@ -48,6 +48,17 @@ export class BreadcrumbService { }), }, ], + manage_processors: [ + { + text: homeBreadcrumbText, + href: `/`, + }, + { + text: i18n.translate('xpack.ingestPipelines.breadcrumb.manageProcessorsLabel', { + defaultMessage: 'Manage processors', + }), + }, + ], }; private setBreadcrumbsHandler?: SetBreadcrumbs; @@ -56,7 +67,7 @@ export class BreadcrumbService { this.setBreadcrumbsHandler = setBreadcrumbsHandler; } - public setBreadcrumbs(type: 'create' | 'home' | 'edit'): void { + public setBreadcrumbs(type: 'create' | 'home' | 'edit' | 'manage_processors'): void { if (!this.setBreadcrumbsHandler) { throw new Error('Breadcrumb service has not been initialized'); } diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts index 7d3e11fea3d89..aa4f95be09b17 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts @@ -13,6 +13,8 @@ const CREATE_PATH = 'create'; const CREATE_FROM_CSV_PATH = 'csv_create'; +const MANAGE_PROCESSORS_PATH = 'manage_processors'; + const _getEditPath = (name: string, encode = true): string => { return `${BASE_PATH}${EDIT_PATH}/${encode ? encodeURIComponent(name) : name}`; }; @@ -33,12 +35,17 @@ const _getCreateFromCsvPath = (): string => { return `${BASE_PATH}${CREATE_FROM_CSV_PATH}`; }; +const _getManageProcessorsPath = (): string => { + return `${BASE_PATH}${MANAGE_PROCESSORS_PATH}`; +}; + export const ROUTES = { list: _getListPath(), edit: _getEditPath(':name', false), create: _getCreatePath(), clone: _getClonePath(':sourceName', false), createFromCsv: _getCreateFromCsvPath(), + manageProcessors: _getManageProcessorsPath(), }; export const getListPath = ({ @@ -52,3 +59,4 @@ export const getCreatePath = (): string => _getCreatePath(); export const getClonePath = ({ clonedPipelineName }: { clonedPipelineName: string }): string => _getClonePath(clonedPipelineName, true); export const getCreateFromCsvPath = (): string => _getCreateFromCsvPath(); +export const getManageProcessorsPath = (): string => _getManageProcessorsPath(); diff --git a/x-pack/plugins/ingest_pipelines/public/index.ts b/x-pack/plugins/ingest_pipelines/public/index.ts index b269245faf520..d7fb12c5477d3 100644 --- a/x-pack/plugins/ingest_pipelines/public/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/index.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { PluginInitializerContext } from '@kbn/core/public'; import { IngestPipelinesPlugin } from './plugin'; -export function plugin() { - return new IngestPipelinesPlugin(); +export function plugin(context: PluginInitializerContext) { + return new IngestPipelinesPlugin(context); } export { INGEST_PIPELINES_APP_LOCATOR, INGEST_PIPELINES_PAGES } from './locator'; diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index ae180b8378af3..75a6139e95933 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -7,11 +7,11 @@ import { i18n } from '@kbn/i18n'; import { Subscription } from 'rxjs'; -import { CoreStart, CoreSetup, Plugin } from '@kbn/core/public'; +import type { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public'; import { PLUGIN_ID } from '../common/constants'; import { uiMetricService, apiService } from './application/services'; -import { SetupDependencies, StartDependencies, ILicense } from './types'; +import type { SetupDependencies, StartDependencies, ILicense, Config } from './types'; import { IngestPipelinesLocatorDefinition } from './locator'; export class IngestPipelinesPlugin @@ -19,6 +19,11 @@ export class IngestPipelinesPlugin { private license: ILicense | null = null; private licensingSubscription?: Subscription; + private readonly config: Config; + + constructor(initializerContext: PluginInitializerContext<Config>) { + this.config = initializerContext.config.get(); + } public setup(coreSetup: CoreSetup<StartDependencies>, plugins: SetupDependencies): void { const { management, usageCollection, share } = plugins; @@ -49,6 +54,9 @@ export class IngestPipelinesPlugin const unmountAppCallback = await mountManagementSection(coreSetup, { ...params, license: this.license, + config: { + enableManageProcessors: this.config.enableManageProcessors !== false, + }, }); return () => { diff --git a/x-pack/plugins/ingest_pipelines/public/types.ts b/x-pack/plugins/ingest_pipelines/public/types.ts index bfa1ac4300b3a..5b1dee11d37e0 100644 --- a/x-pack/plugins/ingest_pipelines/public/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/types.ts @@ -25,3 +25,7 @@ export interface StartDependencies { licensing?: LicensingPluginStart; console?: ConsolePluginStart; } + +export interface Config { + enableManageProcessors: boolean; +} diff --git a/x-pack/plugins/ingest_pipelines/server/config.ts b/x-pack/plugins/ingest_pipelines/server/config.ts new file mode 100644 index 0000000000000..dc3dcf86a6256 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/config.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 { offeringBasedSchema, schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core-plugins-server'; + +const configSchema = schema.object( + { + enableManageProcessors: offeringBasedSchema({ + // Manage processors UI is disabled in serverless; refer to the serverless.yml file as the source of truth + // We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana + serverless: schema.boolean({ defaultValue: true }), + }), + }, + { defaultValue: undefined } +); + +export type IngestPipelinesConfigType = TypeOf<typeof configSchema>; + +export const config: PluginConfigDescriptor<IngestPipelinesConfigType> = { + schema: configSchema, + exposeToBrowser: { + enableManageProcessors: true, + }, +}; diff --git a/x-pack/plugins/ingest_pipelines/server/index.ts b/x-pack/plugins/ingest_pipelines/server/index.ts index aac84c37591db..b48d8214c1264 100644 --- a/x-pack/plugins/ingest_pipelines/server/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/index.ts @@ -5,7 +5,11 @@ * 2.0. */ -export async function plugin() { +import { PluginInitializerContext } from '@kbn/core/server'; + +export { config } from './config'; + +export async function plugin(context: PluginInitializerContext) { const { IngestPipelinesPlugin } = await import('./plugin'); - return new IngestPipelinesPlugin(); + return new IngestPipelinesPlugin(context); } diff --git a/x-pack/plugins/ingest_pipelines/server/plugin.ts b/x-pack/plugins/ingest_pipelines/server/plugin.ts index ea1d9fc01c42a..85ca1691bf392 100644 --- a/x-pack/plugins/ingest_pipelines/server/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/server/plugin.ts @@ -5,17 +5,20 @@ * 2.0. */ -import { CoreSetup, Plugin } from '@kbn/core/server'; +import { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { IngestPipelinesConfigType } from './config'; import { ApiRoutes } from './routes'; import { handleEsError } from './shared_imports'; import { Dependencies } from './types'; export class IngestPipelinesPlugin implements Plugin<void, void, any, any> { private readonly apiRoutes: ApiRoutes; + private readonly config: IngestPipelinesConfigType; - constructor() { + constructor(initContext: PluginInitializerContext<IngestPipelinesConfigType>) { this.apiRoutes = new ApiRoutes(); + this.config = initContext.config.get(); } public setup({ http }: CoreSetup, { security, features }: Dependencies) { @@ -38,6 +41,7 @@ export class IngestPipelinesPlugin implements Plugin<void, void, any, any> { router, config: { isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), + enableManageProcessors: this.config.enableManageProcessors !== false, }, lib: { handleEsError, diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/database/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/database/create.ts new file mode 100644 index 0000000000000..56fef0e159d66 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/database/create.ts @@ -0,0 +1,74 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { API_BASE_PATH } from '../../../../common/constants'; +import { serializeGeoipDatabase } from './serialization'; +import { normalizeDatabaseName } from './normalize_database_name'; + +const bodySchema = schema.object({ + databaseType: schema.oneOf([schema.literal('ipinfo'), schema.literal('maxmind')]), + // maxmind is only needed for "geoip" type + maxmind: schema.maybe(schema.string({ maxLength: 1000 })), + // only allow database names in sync with ES + databaseName: schema.oneOf([ + // geoip names https://github.com/elastic/elasticsearch/blob/f150e2c11df0fe3bef298c55bd867437e50f5f73/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/direct/DatabaseConfiguration.java#L58 + schema.literal('GeoIP2-Anonymous-IP'), + schema.literal('GeoIP2-City'), + schema.literal('GeoIP2-Connection-Type'), + schema.literal('GeoIP2-Country'), + schema.literal('GeoIP2-Domain'), + schema.literal('GeoIP2-Enterprise'), + schema.literal('GeoIP2-ISP'), + // ipinfo names + schema.literal('asn'), + schema.literal('country'), + schema.literal('standard_asn'), + schema.literal('standard_location'), + schema.literal('standard_privacy'), + ]), +}); + +export const registerCreateDatabaseRoute = ({ + router, + lib: { handleEsError }, +}: RouteDependencies): void => { + router.post( + { + path: `${API_BASE_PATH}/databases`, + validate: { + body: bodySchema, + }, + }, + async (ctx, req, res) => { + const { client: clusterClient } = (await ctx.core).elasticsearch; + const { databaseType, databaseName, maxmind } = req.body; + const serializedDatabase = serializeGeoipDatabase({ databaseType, databaseName, maxmind }); + const normalizedDatabaseName = normalizeDatabaseName(databaseName); + + try { + // TODO: Replace this request with the one below when the JS client fixed + await clusterClient.asCurrentUser.transport.request({ + method: 'PUT', + path: `/_ingest/ip_location/database/${normalizedDatabaseName}`, + body: serializedDatabase, + }); + + // This request fails because there is a bug in the JS client + // await clusterClient.asCurrentUser.ingest.putGeoipDatabase({ + // id: normalizedDatabaseName, + // body: serializedDatabase, + // }); + + return res.ok({ body: { name: databaseName, id: normalizedDatabaseName } }); + } catch (error) { + return handleEsError({ error, response: res }); + } + } + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/database/delete.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/database/delete.ts new file mode 100644 index 0000000000000..69dcde1436fd6 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/database/delete.ts @@ -0,0 +1,40 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { API_BASE_PATH } from '../../../../common/constants'; + +const paramsSchema = schema.object({ + database_id: schema.string(), +}); + +export const registerDeleteDatabaseRoute = ({ + router, + lib: { handleEsError }, +}: RouteDependencies): void => { + router.delete( + { + path: `${API_BASE_PATH}/databases/{database_id}`, + validate: { + params: paramsSchema, + }, + }, + async (ctx, req, res) => { + const { client: clusterClient } = (await ctx.core).elasticsearch; + const { database_id: databaseID } = req.params; + + try { + await clusterClient.asCurrentUser.ingest.deleteGeoipDatabase({ id: databaseID }); + + return res.ok(); + } catch (error) { + return handleEsError({ error, response: res }); + } + } + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/database/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/database/index.ts new file mode 100644 index 0000000000000..612b52dbd0643 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/database/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { registerListDatabaseRoute } from './list'; +export { registerCreateDatabaseRoute } from './create'; +export { registerDeleteDatabaseRoute } from './delete'; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/database/list.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/database/list.ts new file mode 100644 index 0000000000000..b3509a5486435 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/database/list.ts @@ -0,0 +1,37 @@ +/* + * 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 { deserializeGeoipDatabase, type GeoipDatabaseFromES } from './serialization'; +import { API_BASE_PATH } from '../../../../common/constants'; +import { RouteDependencies } from '../../../types'; + +export const registerListDatabaseRoute = ({ + router, + lib: { handleEsError }, +}: RouteDependencies): void => { + router.get({ path: `${API_BASE_PATH}/databases`, validate: false }, async (ctx, req, res) => { + const { client: clusterClient } = (await ctx.core).elasticsearch; + + try { + const data = (await clusterClient.asCurrentUser.ingest.getGeoipDatabase()) as { + databases: GeoipDatabaseFromES[]; + }; + + const geoipDatabases = data.databases; + + return res.ok({ body: geoipDatabases.map(deserializeGeoipDatabase) }); + } catch (error) { + const esErrorResponse = handleEsError({ error, response: res }); + if (esErrorResponse.status === 404) { + // ES returns 404 when there are no pipelines + // Instead, we return an empty array and 200 status back to the client + return res.ok({ body: [] }); + } + return esErrorResponse; + } + }); +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/database/normalize_database_name.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/database/normalize_database_name.ts new file mode 100644 index 0000000000000..36f142d91a28d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/database/normalize_database_name.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const normalizeDatabaseName = (databaseName: string): string => { + return databaseName.replace(/\s+/g, '_').toLowerCase(); +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/database/serialization.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/database/serialization.ts new file mode 100644 index 0000000000000..2f2c93ba5334d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/database/serialization.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface GeoipDatabaseFromES { + id: string; + version: number; + modified_date_millis: number; + database: { + name: string; + // maxmind type + maxmind?: { + account_id: string; + }; + // ipinfo type + ipinfo?: {}; + // local type + local?: {}; + // web type + web?: {}; + }; +} + +interface SerializedGeoipDatabase { + name: string; + ipinfo?: {}; + local?: {}; + web?: {}; + maxmind?: { + account_id: string; + }; +} + +const getGeoipType = ({ database }: GeoipDatabaseFromES) => { + if (database.maxmind && database.maxmind.account_id) { + return 'maxmind'; + } + + if (database.ipinfo) { + return 'ipinfo'; + } + + if (database.local) { + return 'local'; + } + + if (database.web) { + return 'web'; + } + + return 'unknown'; +}; + +export const deserializeGeoipDatabase = (geoipDatabase: GeoipDatabaseFromES) => { + const { database, id } = geoipDatabase; + return { + name: database.name, + id, + type: getGeoipType(geoipDatabase), + }; +}; + +export const serializeGeoipDatabase = ({ + databaseType, + databaseName, + maxmind, +}: { + databaseType: 'maxmind' | 'ipinfo' | 'local' | 'web'; + databaseName: string; + maxmind?: string; +}): SerializedGeoipDatabase => { + const database = { name: databaseName } as SerializedGeoipDatabase; + + if (databaseType === 'maxmind') { + database.maxmind = { account_id: maxmind ?? '' }; + } + + if (databaseType === 'ipinfo') { + database.ipinfo = {}; + } + + if (databaseType === 'local') { + database.local = {}; + } + + if (databaseType === 'web') { + database.web = {}; + } + + return database; +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts index aec90d2c3a2eb..7be84d9baad87 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts @@ -20,3 +20,9 @@ export { registerSimulateRoute } from './simulate'; export { registerDocumentsRoute } from './documents'; export { registerParseCsvRoute } from './parse_csv'; + +export { + registerListDatabaseRoute, + registerCreateDatabaseRoute, + registerDeleteDatabaseRoute, +} from './database'; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 29b282b5fbf20..87f0e3e79f07f 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -6,9 +6,14 @@ */ import { Privileges } from '@kbn/es-ui-shared-plugin/common'; +import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../types'; import { API_BASE_PATH, APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../../common/constants'; +const requiredPrivilegesMap = { + ingest_pipelines: APP_CLUSTER_REQUIRED_PRIVILEGES, + manage_processors: ['manage'], +}; const extractMissingPrivileges = (privilegesObject: { [key: string]: boolean } = {}): string[] => Object.keys(privilegesObject).reduce((privileges: string[], privilegeName: string): string[] => { if (!privilegesObject[privilegeName]) { @@ -20,10 +25,18 @@ const extractMissingPrivileges = (privilegesObject: { [key: string]: boolean } = export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) => { router.get( { - path: `${API_BASE_PATH}/privileges`, - validate: false, + path: `${API_BASE_PATH}/privileges/{permissions_type}`, + validate: { + params: schema.object({ + permissions_type: schema.oneOf([ + schema.literal('ingest_pipelines'), + schema.literal('manage_processors'), + ]), + }), + }, }, async (ctx, req, res) => { + const permissionsType = req.params.permissions_type; const privilegesResult: Privileges = { hasAllPrivileges: true, missingPrivileges: { @@ -38,9 +51,10 @@ export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) = const { client: clusterClient } = (await ctx.core).elasticsearch; + const requiredPrivileges = requiredPrivilegesMap[permissionsType]; const { has_all_requested: hasAllPrivileges, cluster } = await clusterClient.asCurrentUser.security.hasPrivileges({ - body: { cluster: APP_CLUSTER_REQUIRED_PRIVILEGES }, + body: { cluster: requiredPrivileges }, }); if (!hasAllPrivileges) { diff --git a/x-pack/plugins/ingest_pipelines/server/routes/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/index.ts index d3d74b31c1013..9a74a285fb5e4 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/index.ts @@ -16,6 +16,9 @@ import { registerSimulateRoute, registerDocumentsRoute, registerParseCsvRoute, + registerListDatabaseRoute, + registerCreateDatabaseRoute, + registerDeleteDatabaseRoute, } from './api'; export class ApiRoutes { @@ -28,5 +31,10 @@ export class ApiRoutes { registerSimulateRoute(dependencies); registerDocumentsRoute(dependencies); registerParseCsvRoute(dependencies); + if (dependencies.config.enableManageProcessors) { + registerListDatabaseRoute(dependencies); + registerCreateDatabaseRoute(dependencies); + registerDeleteDatabaseRoute(dependencies); + } } } diff --git a/x-pack/plugins/ingest_pipelines/server/types.ts b/x-pack/plugins/ingest_pipelines/server/types.ts index 34c821b90e79c..8204e7f21e93d 100644 --- a/x-pack/plugins/ingest_pipelines/server/types.ts +++ b/x-pack/plugins/ingest_pipelines/server/types.ts @@ -19,6 +19,7 @@ export interface RouteDependencies { router: IRouter; config: { isSecurityEnabled: () => boolean; + enableManageProcessors: boolean; }; lib: { handleEsError: typeof handleEsError; diff --git a/x-pack/plugins/ingest_pipelines/tsconfig.json b/x-pack/plugins/ingest_pipelines/tsconfig.json index 7570a8f659167..5792ac1b9fda1 100644 --- a/x-pack/plugins/ingest_pipelines/tsconfig.json +++ b/x-pack/plugins/ingest_pipelines/tsconfig.json @@ -36,7 +36,9 @@ "@kbn/react-kibana-context-theme", "@kbn/unsaved-changes-prompt", "@kbn/core-http-browser-mocks", - "@kbn/shared-ux-table-persist" + "@kbn/shared-ux-table-persist", + "@kbn/core-http-browser", + "@kbn/core-plugins-server" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/databases.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/databases.ts new file mode 100644 index 0000000000000..93a7ccc7d4088 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/databases.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const ingestPipelines = getService('ingestPipelines'); + const url = `/api/ingest_pipelines/databases`; + const databaseName = 'GeoIP2-Anonymous-IP'; + const normalizedDatabaseName = 'geoip2-anonymous-ip'; + + describe('Manage databases', function () { + after(async () => { + await ingestPipelines.api.deleteGeoipDatabases(); + }); + + describe('Create', () => { + it('creates a geoip database when using a correct database name', async () => { + const database = { maxmind: '123456', databaseName }; + const { body } = await supertest + .post(url) + .set('kbn-xsrf', 'xxx') + .send(database) + .expect(200); + + expect(body).to.eql({ + name: databaseName, + id: normalizedDatabaseName, + }); + }); + + it('creates a geoip database when using an incorrect database name', async () => { + const database = { maxmind: '123456', databaseName: 'Test' }; + await supertest.post(url).set('kbn-xsrf', 'xxx').send(database).expect(400); + }); + }); + + describe('List', () => { + it('returns existing databases', async () => { + const { body } = await supertest.get(url).set('kbn-xsrf', 'xxx').expect(200); + expect(body).to.eql([ + { + id: normalizedDatabaseName, + name: databaseName, + type: 'maxmind', + }, + ]); + }); + }); + + describe('Delete', () => { + it('deletes a geoip database', async () => { + await supertest + .delete(`${url}/${normalizedDatabaseName}`) + .set('kbn-xsrf', 'xxx') + .expect(200); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts new file mode 100644 index 0000000000000..0afcb720dc3cd --- /dev/null +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Ingest pipelines', () => { + loadTestFile(require.resolve('./databases')); + }); +} diff --git a/x-pack/test/api_integration/services/ingest_pipelines/geoip_databases.ts b/x-pack/test/api_integration/services/ingest_pipelines/geoip_databases.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/test/api_integration/services/ingest_pipelines/geoip_databases.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/test/api_integration/services/ingest_pipelines/lib/api.ts b/x-pack/test/api_integration/services/ingest_pipelines/lib/api.ts index e1f4b8a430314..493540afa4710 100644 --- a/x-pack/test/api_integration/services/ingest_pipelines/lib/api.ts +++ b/x-pack/test/api_integration/services/ingest_pipelines/lib/api.ts @@ -70,5 +70,20 @@ export function IngestPipelinesAPIProvider({ getService }: FtrProviderContext) { return await es.indices.delete({ index: indexName }); }, + + async deleteGeoipDatabases() { + const { databases } = await es.ingest.getGeoipDatabase(); + // Remove all geoip databases + const databaseIds = databases.map((database: { id: string }) => database.id); + + const deleteDatabase = (id: string) => + es.ingest.deleteGeoipDatabase({ + id, + }); + + return Promise.all(databaseIds.map(deleteDatabase)).catch((err) => { + log.debug(`[Cleanup error] Error deleting ES resources: ${err.message}`); + }); + }, }; } diff --git a/x-pack/test/functional/apps/ingest_pipelines/index.ts b/x-pack/test/functional/apps/ingest_pipelines/index.ts index 3c585319cfe13..1f77f5078de9f 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/index.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/index.ts @@ -11,5 +11,6 @@ export default ({ loadTestFile }: FtrProviderContext) => { describe('Ingest pipelines app', function () { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./ingest_pipelines')); + loadTestFile(require.resolve('./manage_processors')); }); }; diff --git a/x-pack/test/functional/apps/ingest_pipelines/manage_processors.ts b/x-pack/test/functional/apps/ingest_pipelines/manage_processors.ts new file mode 100644 index 0000000000000..a4951a2829fd0 --- /dev/null +++ b/x-pack/test/functional/apps/ingest_pipelines/manage_processors.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'ingestPipelines', 'savedObjects']); + const security = getService('security'); + const maxMindDatabaseName = 'GeoIP2-Anonymous-IP'; + const ipInfoDatabaseName = 'ASN'; + + // TODO: Fix flaky tests + describe.skip('Ingest Pipelines: Manage Processors', function () { + this.tags('smoke'); + before(async () => { + await security.testUser.setRoles(['manage_processors_user']); + }); + beforeEach(async () => { + await pageObjects.common.navigateToApp('ingestPipelines'); + await pageObjects.ingestPipelines.navigateToManageProcessorsPage(); + }); + after(async () => { + await security.testUser.restoreDefaults(); + }); + + it('Empty list prompt', async () => { + const promptExists = await pageObjects.ingestPipelines.geoipEmptyListPromptExists(); + expect(promptExists).to.be(true); + }); + + it('Create a MaxMind database', async () => { + await pageObjects.ingestPipelines.openCreateDatabaseModal(); + await pageObjects.ingestPipelines.fillAddDatabaseForm( + 'MaxMind', + 'GeoIP2 Anonymous IP', + '123456' + ); + await pageObjects.ingestPipelines.clickAddDatabaseButton(); + + // Wait for new row to gets displayed + await pageObjects.common.sleep(1000); + + const databasesList = await pageObjects.ingestPipelines.getGeoipDatabases(); + const databaseExists = Boolean( + databasesList.find((databaseRow) => databaseRow.includes(maxMindDatabaseName)) + ); + + expect(databaseExists).to.be(true); + }); + + it('Create an IPInfo database', async () => { + await pageObjects.ingestPipelines.openCreateDatabaseModal(); + await pageObjects.ingestPipelines.fillAddDatabaseForm('IPInfo', ipInfoDatabaseName); + await pageObjects.ingestPipelines.clickAddDatabaseButton(); + + // Wait for new row to gets displayed + await pageObjects.common.sleep(1000); + + const databasesList = await pageObjects.ingestPipelines.getGeoipDatabases(); + const databaseExists = Boolean( + databasesList.find((databaseRow) => databaseRow.includes(ipInfoDatabaseName)) + ); + + expect(databaseExists).to.be(true); + }); + + it('Table contains database name and maxmind type', async () => { + const databasesList = await pageObjects.ingestPipelines.getGeoipDatabases(); + const maxMindDatabaseRow = databasesList.find((database) => + database.includes(maxMindDatabaseName) + ); + expect(maxMindDatabaseRow).to.contain(maxMindDatabaseName); + expect(maxMindDatabaseRow).to.contain('MaxMind'); + + const ipInfoDatabaseRow = databasesList.find((database) => + database.includes(ipInfoDatabaseName) + ); + expect(ipInfoDatabaseRow).to.contain(ipInfoDatabaseName); + expect(ipInfoDatabaseRow).to.contain('IPInfo'); + }); + + it('Modal to delete a database', async () => { + // Delete both databases + await pageObjects.ingestPipelines.deleteDatabase(0); + await pageObjects.ingestPipelines.deleteDatabase(0); + const promptExists = await pageObjects.ingestPipelines.geoipEmptyListPromptExists(); + expect(promptExists).to.be(true); + }); + }); +}; diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 8d1e875dabccc..b35d1f6b6673c 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -640,6 +640,20 @@ export default async function ({ readConfigFile }) { ], }, + manage_processors_user: { + elasticsearch: { + cluster: ['manage'], + }, + kibana: [ + { + feature: { + advancedSettings: ['read'], + }, + spaces: ['*'], + }, + ], + }, + license_management_user: { elasticsearch: { cluster: ['manage'], diff --git a/x-pack/test/functional/page_objects/ingest_pipelines_page.ts b/x-pack/test/functional/page_objects/ingest_pipelines_page.ts index 218a34e5c1ae2..b62d34b114f4b 100644 --- a/x-pack/test/functional/page_objects/ingest_pipelines_page.ts +++ b/x-pack/test/functional/page_objects/ingest_pipelines_page.ts @@ -7,12 +7,14 @@ import path from 'path'; import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function IngestPipelinesPageProvider({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const pageObjects = getPageObjects(['header', 'common']); const aceEditor = getService('aceEditor'); + const retry = getService('retry'); return { async sectionHeadingText() { @@ -113,5 +115,56 @@ export function IngestPipelinesPageProvider({ getService, getPageObjects }: FtrP await testSubjects.click('tablePaginationPopoverButton'); await testSubjects.click(`tablePagination-50-rows`); }, + + async navigateToManageProcessorsPage() { + await testSubjects.click('manageProcessorsLink'); + await retry.waitFor('Manage Processors page title to be displayed', async () => { + return await testSubjects.isDisplayed('manageProcessorsTitle'); + }); + }, + + async geoipEmptyListPromptExists() { + return await testSubjects.exists('geoipEmptyListPrompt'); + }, + + async openCreateDatabaseModal() { + await testSubjects.click('addGeoipDatabaseButton'); + }, + + async fillAddDatabaseForm(databaseType: string, databaseName: string, maxmind?: string) { + await testSubjects.setValue('databaseTypeSelect', databaseType); + + // Wait for the rest of the fields to get displayed + await pageObjects.common.sleep(1000); + expect(await testSubjects.exists('databaseNameSelect')).to.be(true); + + if (maxmind) { + await testSubjects.setValue('maxmindField', maxmind); + } + await testSubjects.setValue('databaseNameSelect', databaseName); + }, + + async clickAddDatabaseButton() { + // Wait for button to get enabled + await pageObjects.common.sleep(1000); + await testSubjects.click('addGeoipDatabaseSubmit'); + }, + + async getGeoipDatabases() { + const databases = await testSubjects.findAll('geoipDatabaseListRow'); + + const getDatabaseRow = async (database: WebElementWrapper) => { + return await database.getVisibleText(); + }; + + return await Promise.all(databases.map((database) => getDatabaseRow(database))); + }, + + async deleteDatabase(index: number) { + const deleteButtons = await testSubjects.findAll('deleteGeoipDatabaseButton'); + await deleteButtons.at(index)?.click(); + await testSubjects.setValue('geoipDatabaseConfirmation', 'delete'); + await testSubjects.click('deleteGeoipDatabaseSubmit'); + }, }; } From 9c2a0418f51bb87f130c3ac7d139bad4d1aa7cc5 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:01:02 +0300 Subject: [PATCH 58/84] [Cloud Security] 3P callout displayed in tables (#196335) --- .../pages/configurations/configurations.tsx | 4 ++ .../public/pages/findings/findings.tsx | 34 ++------------- .../third_party_integrations_callout.tsx | 41 +++++++++++++++++++ .../pages/vulnerabilities/vulnerabilities.tsx | 4 ++ 4 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/pages/findings/third_party_integrations_callout.tsx diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx index d070d2cd9ec4b..4cc5ea679ba80 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx @@ -12,6 +12,8 @@ import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_ import { CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX } from '@kbn/cloud-security-posture-common'; import { findingsNavigation } from '@kbn/cloud-security-posture'; import { useDataView } from '@kbn/cloud-security-posture/src/hooks/use_data_view'; +import { EuiSpacer } from '@elastic/eui'; +import { ThirdPartyIntegrationsCallout } from '../findings/third_party_integrations_callout'; import { NoFindingsStates } from '../../components/no_findings_states'; import { CloudPosturePage, defaultLoadingRenderer } from '../../components/cloud_posture_page'; import { cloudPosturePages } from '../../common/navigation/constants'; @@ -45,6 +47,8 @@ export const Configurations = () => { return ( <CloudPosturePage query={dataViewQuery}> + <EuiSpacer /> + <ThirdPartyIntegrationsCallout /> <Routes> <Route exact diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx index 6b1dc4dacdf68..00837a3629893 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx @@ -6,20 +6,15 @@ */ import React from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { EuiSpacer, EuiTab, EuiTabs, EuiTitle, EuiCallOut, EuiButton } from '@elastic/eui'; +import { EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { Redirect, useHistory, useLocation, matchPath } from 'react-router-dom'; import { Routes, Route } from '@kbn/shared-ux-router'; import { findingsNavigation } from '@kbn/cloud-security-posture'; import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; -import { i18n } from '@kbn/i18n'; -import { useAdd3PIntegrationRoute } from '../../common/api/use_wiz_integration_route'; import { Configurations } from '../configurations'; import { cloudPosturePages } from '../../common/navigation/constants'; -import { - LOCAL_STORAGE_3P_INTEGRATIONS_CALLOUT_KEY, - LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY, -} from '../../common/constants'; +import { LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY } from '../../common/constants'; import { VULNERABILITIES_INDEX_NAME, FINDINGS_INDEX_NAME } from '../../../common/constants'; import { getStatusForIndexName } from '../../../common/utils/helpers'; import { Vulnerabilities } from '../vulnerabilities'; @@ -64,10 +59,7 @@ const FindingsTabRedirecter = ({ lastTabSelected }: { lastTabSelected?: Findings export const Findings = () => { const history = useHistory(); const location = useLocation(); - const wizAddIntegrationLink = useAdd3PIntegrationRoute('wiz'); - const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage( - LOCAL_STORAGE_3P_INTEGRATIONS_CALLOUT_KEY - ); + // restore the users most recent tab selection const [lastTabSelected, setLastTabSelected] = useLocalStorage<FindingsTabKey>( LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY @@ -109,26 +101,6 @@ export const Findings = () => { </h1> </EuiTitle> <EuiSpacer /> - {!userHasDismissedCallout && ( - <> - <EuiCallOut - title={i18n.translate('xpack.csp.findings.3pIntegrationsCallout.title', { - defaultMessage: - "New! Ingest your cloud security product's data into Elastic for centralized analytics, hunting, investigations, visualizations, and more", - })} - iconType="cheer" - onDismiss={() => setUserHasDismissedCallout(true)} - > - <EuiButton href={wizAddIntegrationLink}> - <FormattedMessage - id="xpack.csp.findings.3pIntegrationsCallout.buttonTitle" - defaultMessage="Integrate Wiz" - /> - </EuiButton> - </EuiCallOut> - <EuiSpacer /> - </> - )} <EuiTabs size="l"> <EuiTab key="configurations" diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/third_party_integrations_callout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/third_party_integrations_callout.tsx new file mode 100644 index 0000000000000..85b808391c7d3 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/third_party_integrations_callout.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { EuiButton, EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useAdd3PIntegrationRoute } from '../../common/api/use_wiz_integration_route'; +import { LOCAL_STORAGE_3P_INTEGRATIONS_CALLOUT_KEY } from '../../common/constants'; + +export const ThirdPartyIntegrationsCallout = () => { + const wizAddIntegrationLink = useAdd3PIntegrationRoute('wiz'); + const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage( + LOCAL_STORAGE_3P_INTEGRATIONS_CALLOUT_KEY + ); + + if (userHasDismissedCallout) return null; + + return ( + <EuiCallOut + title={i18n.translate('xpack.csp.findings.3pIntegrationsCallout.title', { + defaultMessage: + "New! Ingest your cloud security product's data into Elastic for centralized analytics, hunting, investigations, visualizations, and more", + })} + iconType="cheer" + onDismiss={() => setUserHasDismissedCallout(true)} + > + <EuiButton href={wizAddIntegrationLink}> + <FormattedMessage + id="xpack.csp.findings.3pIntegrationsCallout.buttonTitle" + defaultMessage="Integrate Wiz" + /> + </EuiButton> + </EuiCallOut> + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx index 659d1c9d5e245..90ffc4849c0b7 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx @@ -9,6 +9,8 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import { findingsNavigation } from '@kbn/cloud-security-posture'; import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; import { useDataView } from '@kbn/cloud-security-posture/src/hooks/use_data_view'; +import { EuiSpacer } from '@elastic/eui'; +import { ThirdPartyIntegrationsCallout } from '../findings/third_party_integrations_callout'; import { VULNERABILITIES_PAGE } from './test_subjects'; import { CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX } from '../../../common/constants'; import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states'; @@ -34,6 +36,8 @@ export const Vulnerabilities = () => { return ( <CloudPosturePage query={dataViewQuery}> + <EuiSpacer /> + <ThirdPartyIntegrationsCallout /> <div data-test-subj={VULNERABILITIES_PAGE}> <Routes> <Route From dbe6d82584c99fb8eda7fa117e220a97cfb0c33b Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@elastic.co> Date: Tue, 15 Oct 2024 13:07:10 -0500 Subject: [PATCH 59/84] Revert "[Security Solution] [Attack discovery] Output chunking / refinement, LangGraph migration, and evaluation improvements (#195669)" This reverts commit 2c21adb8faafc0016ad7a6591837118f6bdf0907. --- .../get_raw_data_or_default/index.test.ts | 28 - .../helpers/get_raw_data_or_default/index.ts | 13 - .../helpers/is_raw_data_valid/index.test.ts | 51 - .../alerts/helpers/is_raw_data_valid/index.ts | 11 - .../size_is_out_of_range/index.test.ts | 47 - .../helpers/size_is_out_of_range/index.ts | 12 - .../impl/alerts/helpers/types.ts | 14 - .../attack_discovery/common_attributes.gen.ts | 4 +- .../common_attributes.schema.yaml | 2 + .../evaluation/post_evaluate_route.gen.ts | 2 - .../post_evaluate_route.schema.yaml | 4 - .../kbn-elastic-assistant-common/index.ts | 16 - .../alerts_settings/alerts_settings.tsx | 3 +- .../alerts_settings_management.tsx | 1 - .../evaluation_settings.tsx | 64 +- .../evaluation_settings/translations.ts | 30 - .../impl/assistant_context/constants.tsx | 5 - .../impl/assistant_context/index.tsx | 5 +- .../impl/knowledge_base/alerts_range.tsx | 64 +- .../packages/kbn-elastic-assistant/index.ts | 20 - x-pack/plugins/elastic_assistant/README.md | 10 +- .../docs/img/default_assistant_graph.png | Bin 29798 -> 30104 bytes .../img/default_attack_discovery_graph.png | Bin 22551 -> 0 bytes .../scripts/draw_graph_script.ts | 46 +- .../__mocks__/attack_discovery_schema.mock.ts | 2 +- .../server/__mocks__/data_clients.mock.ts | 2 +- .../server/__mocks__/request_context.ts | 2 +- .../server/__mocks__/response.ts | 2 +- .../create_attack_discovery.test.ts | 4 +- .../create_attack_discovery.ts | 4 +- .../field_maps_configuration.ts | 0 .../find_all_attack_discoveries.ts | 4 +- ...d_attack_discovery_by_connector_id.test.ts | 2 +- .../find_attack_discovery_by_connector_id.ts | 4 +- .../get_attack_discovery.test.ts | 2 +- .../attack_discovery}/get_attack_discovery.ts | 4 +- .../attack_discovery}/index.ts | 15 +- .../attack_discovery}/transforms.ts | 2 +- .../attack_discovery}/types.ts | 4 +- .../update_attack_discovery.test.ts | 4 +- .../update_attack_discovery.ts | 6 +- .../server/ai_assistant_service/index.ts | 4 +- .../evaluation/__mocks__/mock_examples.ts | 55 - .../evaluation/__mocks__/mock_runs.ts | 53 - .../attack_discovery/evaluation/constants.ts | 911 ----------- .../evaluation/example_input/index.test.ts | 75 - .../evaluation/example_input/index.ts | 52 - .../get_default_prompt_template/index.test.ts | 42 - .../get_default_prompt_template/index.ts | 33 - .../index.test.ts | 125 -- .../index.ts | 29 - .../index.test.ts | 117 -- .../index.ts | 27 - .../get_custom_evaluator/index.test.ts | 98 -- .../helpers/get_custom_evaluator/index.ts | 69 - .../index.test.ts | 79 - .../index.ts | 39 - .../helpers/get_evaluator_llm/index.test.ts | 161 -- .../helpers/get_evaluator_llm/index.ts | 65 - .../get_graph_input_overrides/index.test.ts | 121 -- .../get_graph_input_overrides/index.ts | 29 - .../lib/attack_discovery/evaluation/index.ts | 122 -- .../evaluation/run_evaluations/index.ts | 113 -- .../constants.ts | 21 - .../index.test.ts | 22 - .../get_generate_or_end_decision/index.ts | 9 - .../edges/generate_or_end/index.test.ts | 72 - .../edges/generate_or_end/index.ts | 38 - .../index.test.ts | 43 - .../index.ts | 28 - .../helpers/get_should_end/index.test.ts | 60 - .../helpers/get_should_end/index.ts | 16 - .../generate_or_refine_or_end/index.test.ts | 118 -- .../edges/generate_or_refine_or_end/index.ts | 66 - .../edges/helpers/get_has_results/index.ts | 11 - .../helpers/get_has_zero_alerts/index.ts | 12 - .../get_refine_or_end_decision/index.ts | 25 - .../helpers/get_should_end/index.ts | 16 - .../edges/refine_or_end/index.ts | 61 - .../get_retrieve_or_generate/index.ts | 13 - .../index.ts | 36 - .../index.ts | 14 - .../helpers/get_max_retries_reached/index.ts | 14 - .../default_attack_discovery_graph/index.ts | 122 -- ...en_and_acknowledged_alerts_qery_results.ts | 25 - ...n_and_acknowledged_alerts_query_results.ts | 1396 ----------------- .../discard_previous_generations/index.ts | 30 - .../get_alerts_context_prompt/index.ts | 22 - .../get_anonymized_alerts_from_state/index.ts | 11 - .../get_use_unrefined_results/index.ts | 27 - .../nodes/generate/index.ts | 154 -- .../nodes/generate/schema/index.ts | 84 - .../index.ts | 20 - .../nodes/helpers/extract_json/index.test.ts | 67 - .../nodes/helpers/extract_json/index.ts | 17 - .../generations_are_repeating/index.test.tsx | 90 -- .../generations_are_repeating/index.tsx | 25 - .../index.ts | 34 - .../nodes/helpers/get_combined/index.ts | 14 - .../index.ts | 43 - .../helpers/get_continue_prompt/index.ts | 15 - .../index.ts | 9 - .../helpers/get_output_parser/index.test.ts | 31 - .../nodes/helpers/get_output_parser/index.ts | 13 - .../helpers/parse_combined_or_throw/index.ts | 53 - .../helpers/response_is_hallucinated/index.ts | 9 - .../discard_previous_refinements/index.ts | 30 - .../get_combined_refine_prompt/index.ts | 48 - .../get_default_refine_prompt/index.ts | 11 - .../get_use_unrefined_results/index.ts | 17 - .../nodes/refine/index.ts | 166 -- .../anonymized_alerts_retriever/index.ts | 74 - .../nodes/retriever/index.ts | 70 - .../state/index.ts | 86 - .../default_attack_discovery_graph/types.ts | 28 - .../server/lib/langchain/graphs/index.ts | 35 +- .../cancel_attack_discovery.test.ts | 24 +- .../cancel => }/cancel_attack_discovery.ts | 10 +- .../{get => }/get_attack_discovery.test.ts | 25 +- .../{get => }/get_attack_discovery.ts | 8 +- .../routes/attack_discovery/helpers.test.ts | 805 ++++++++++ .../attack_discovery/{helpers => }/helpers.ts | 231 ++- .../attack_discovery/helpers/helpers.test.ts | 273 ---- .../post/helpers/handle_graph_error/index.tsx | 73 - .../invoke_attack_discovery_graph/index.tsx | 127 -- .../helpers/request_is_valid/index.test.tsx | 87 - .../post/helpers/request_is_valid/index.tsx | 33 - .../throw_if_error_counts_exceeded/index.ts | 44 - .../translations.ts | 28 - .../{post => }/post_attack_discovery.test.ts | 40 +- .../{post => }/post_attack_discovery.ts | 80 +- .../evaluate/get_graphs_from_names/index.ts | 35 - .../server/routes/evaluate/post_evaluate.ts | 43 +- .../server/routes/evaluate/utils.ts | 2 +- .../elastic_assistant/server/routes/index.ts | 4 +- .../server/routes/register_routes.ts | 6 +- .../plugins/elastic_assistant/server/types.ts | 4 +- .../actionable_summary/index.tsx | 43 +- .../attack_discovery_panel/index.tsx | 11 +- .../attack_discovery_panel/title/index.tsx | 27 +- .../get_attack_discovery_markdown.ts | 2 +- .../attack_discovery/hooks/use_poll_api.tsx | 6 +- .../empty_prompt/animated_counter/index.tsx | 2 +- .../pages/empty_prompt/index.test.tsx | 72 +- .../pages/empty_prompt/index.tsx | 29 +- .../helpers/show_empty_states/index.ts | 36 - .../pages/empty_states/index.test.tsx | 33 +- .../pages/empty_states/index.tsx | 44 +- .../attack_discovery/pages/failure/index.tsx | 48 +- .../pages/failure/translations.ts | 13 +- .../attack_discovery/pages/generate/index.tsx | 36 - .../pages/header/index.test.tsx | 13 - .../attack_discovery/pages/header/index.tsx | 16 +- .../settings_modal/alerts_settings/index.tsx | 77 - .../header/settings_modal/footer/index.tsx | 57 - .../pages/header/settings_modal/index.tsx | 160 -- .../settings_modal/is_tour_enabled/index.ts | 18 - .../header/settings_modal/translations.ts | 81 - .../attack_discovery/pages/helpers.test.ts | 4 - .../public/attack_discovery/pages/helpers.ts | 31 +- .../public/attack_discovery/pages/index.tsx | 104 +- .../pages/loading_callout/index.test.tsx | 3 +- .../pages/loading_callout/index.tsx | 13 +- .../get_loading_callout_alerts_count/index.ts | 24 - .../loading_messages/index.test.tsx | 4 +- .../loading_messages/index.tsx | 16 +- .../pages/no_alerts/index.test.tsx | 2 +- .../pages/no_alerts/index.tsx | 17 +- .../attack_discovery/pages/results/index.tsx | 112 -- .../use_attack_discovery/helpers.test.ts | 25 +- .../use_attack_discovery/helpers.ts | 11 +- .../use_attack_discovery/index.test.tsx | 33 +- .../use_attack_discovery/index.tsx | 17 +- .../attack_discovery_tool.test.ts | 340 ++++ .../attack_discovery/attack_discovery_tool.ts | 115 ++ .../get_anonymized_alerts.test.ts} | 18 +- .../get_anonymized_alerts.ts} | 14 +- .../get_attack_discovery_prompt.test.ts} | 17 +- .../get_attack_discovery_prompt.ts | 20 + .../get_output_parser.test.ts | 31 + .../attack_discovery/get_output_parser.ts | 80 + .../server/assistant/tools/index.ts | 2 + .../tools}/mock/mock_anonymization_fields.ts | 0 ...pen_and_acknowledged_alerts_query.test.ts} | 2 +- ...get_open_and_acknowledged_alerts_query.ts} | 7 +- .../helpers.test.ts | 117 ++ .../open_and_acknowledged_alerts/helpers.ts | 22 + .../open_and_acknowledged_alerts_tool.test.ts | 3 +- .../open_and_acknowledged_alerts_tool.ts | 10 +- .../plugins/security_solution/tsconfig.json | 1 + 190 files changed, 2148 insertions(+), 8378 deletions(-) delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts delete mode 100644 x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/create_attack_discovery => ai_assistant_data_clients/attack_discovery}/create_attack_discovery.test.ts (94%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/create_attack_discovery => ai_assistant_data_clients/attack_discovery}/create_attack_discovery.ts (95%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/field_maps_configuration => ai_assistant_data_clients/attack_discovery}/field_maps_configuration.ts (100%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/find_all_attack_discoveries => ai_assistant_data_clients/attack_discovery}/find_all_attack_discoveries.ts (92%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/find_attack_discovery_by_connector_id => ai_assistant_data_clients/attack_discovery}/find_attack_discovery_by_connector_id.test.ts (95%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/find_attack_discovery_by_connector_id => ai_assistant_data_clients/attack_discovery}/find_attack_discovery_by_connector_id.ts (93%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/get_attack_discovery => ai_assistant_data_clients/attack_discovery}/get_attack_discovery.test.ts (95%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/get_attack_discovery => ai_assistant_data_clients/attack_discovery}/get_attack_discovery.ts (93%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence => ai_assistant_data_clients/attack_discovery}/index.ts (92%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/transforms => ai_assistant_data_clients/attack_discovery}/transforms.ts (98%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence => ai_assistant_data_clients/attack_discovery}/types.ts (93%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/update_attack_discovery => ai_assistant_data_clients/attack_discovery}/update_attack_discovery.test.ts (97%) rename x-pack/plugins/elastic_assistant/server/{lib/attack_discovery/persistence/update_attack_discovery => ai_assistant_data_clients/attack_discovery}/update_attack_discovery.ts (95%) delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{post/cancel => }/cancel_attack_discovery.test.ts (80%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{post/cancel => }/cancel_attack_discovery.ts (91%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{get => }/get_attack_discovery.test.ts (85%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{get => }/get_attack_discovery.ts (92%) create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{helpers => }/helpers.ts (55%) delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{post => }/post_attack_discovery.test.ts (79%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{post => }/post_attack_discovery.ts (79%) delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts delete mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts rename x-pack/plugins/{elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts => security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts} (90%) rename x-pack/plugins/{elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts => security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts} (77%) rename x-pack/plugins/{elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts => security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts} (70%) create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts rename x-pack/plugins/{elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph => security_solution/server/assistant/tools}/mock/mock_anonymization_fields.ts (100%) rename x-pack/{packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts => plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts} (96%) rename x-pack/{packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts => plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts} (87%) create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts deleted file mode 100644 index 899b156d21767..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getRawDataOrDefault } from '.'; - -describe('getRawDataOrDefault', () => { - it('returns the raw data when it is valid', () => { - const rawData = { - field1: [1, 2, 3], - field2: ['a', 'b', 'c'], - }; - - expect(getRawDataOrDefault(rawData)).toEqual(rawData); - }); - - it('returns an empty object when the raw data is invalid', () => { - const rawData = { - field1: [1, 2, 3], - field2: 'invalid', - }; - - expect(getRawDataOrDefault(rawData)).toEqual({}); - }); -}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts deleted file mode 100644 index edbe320c95305..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isRawDataValid } from '../is_raw_data_valid'; -import type { MaybeRawData } from '../types'; - -/** Returns the raw data if it valid, or a default if it's not */ -export const getRawDataOrDefault = (rawData: MaybeRawData): Record<string, unknown[]> => - isRawDataValid(rawData) ? rawData : {}; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts deleted file mode 100644 index cc205250e84db..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isRawDataValid } from '.'; - -describe('isRawDataValid', () => { - it('returns true for valid raw data', () => { - const rawData = { - field1: [1, 2, 3], // the Fields API may return a number array - field2: ['a', 'b', 'c'], // the Fields API may return a string array - }; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns true when a field array is empty', () => { - const rawData = { - field1: [1, 2, 3], // the Fields API may return a number array - field2: ['a', 'b', 'c'], // the Fields API may return a string array - field3: [], // the Fields API may return an empty array - }; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns false when a field does not have an array of values', () => { - const rawData = { - field1: [1, 2, 3], - field2: 'invalid', - }; - - expect(isRawDataValid(rawData)).toBe(false); - }); - - it('returns true for empty raw data', () => { - const rawData = {}; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns false when raw data is an unexpected type', () => { - const rawData = 1234; - - // @ts-expect-error - expect(isRawDataValid(rawData)).toBe(false); - }); -}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts deleted file mode 100644 index 1a9623b15ea98..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MaybeRawData } from '../types'; - -export const isRawDataValid = (rawData: MaybeRawData): rawData is Record<string, unknown[]> => - typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x])); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts deleted file mode 100644 index b118a5c94b26e..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { sizeIsOutOfRange } from '.'; -import { MAX_SIZE, MIN_SIZE } from '../types'; - -describe('sizeIsOutOfRange', () => { - it('returns true when size is undefined', () => { - const size = undefined; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns true when size is less than MIN_SIZE', () => { - const size = MIN_SIZE - 1; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns true when size is greater than MAX_SIZE', () => { - const size = MAX_SIZE + 1; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns false when size is exactly MIN_SIZE', () => { - const size = MIN_SIZE; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - - it('returns false when size is exactly MAX_SIZE', () => { - const size = MAX_SIZE; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - - it('returns false when size is within the valid range', () => { - const size = MIN_SIZE + 1; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); -}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts deleted file mode 100644 index b2a93b79cbb42..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MAX_SIZE, MIN_SIZE } from '../types'; - -/** Return true if the provided size is out of range */ -export const sizeIsOutOfRange = (size?: number): boolean => - size == null || size < MIN_SIZE || size > MAX_SIZE; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts deleted file mode 100644 index 5c81c99ce5732..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; - -export const MIN_SIZE = 10; -export const MAX_SIZE = 10000; - -/** currently the same shape as "fields" property in the ES response */ -export type MaybeRawData = SearchResponse['fields'] | undefined; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts index 8ade6084fd7de..9599e8596e553 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts @@ -39,7 +39,7 @@ export const AttackDiscovery = z.object({ /** * A short (no more than a sentence) summary of the attack discovery featuring only the host.name and user.name fields (when they are applicable), using the same syntax */ - entitySummaryMarkdown: z.string().optional(), + entitySummaryMarkdown: z.string(), /** * An array of MITRE ATT&CK tactic for the attack discovery */ @@ -55,7 +55,7 @@ export const AttackDiscovery = z.object({ /** * The time the attack discovery was generated */ - timestamp: NonEmptyString.optional(), + timestamp: NonEmptyString, }); /** diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml index 3adf2f7836804..dcb72147f9408 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml @@ -12,7 +12,9 @@ components: required: - 'alertIds' - 'detailsMarkdown' + - 'entitySummaryMarkdown' - 'summaryMarkdown' + - 'timestamp' - 'title' properties: alertIds: diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts index a0cbc22282c7b..b6d51b9bea3fc 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts @@ -22,12 +22,10 @@ export type PostEvaluateBody = z.infer<typeof PostEvaluateBody>; export const PostEvaluateBody = z.object({ graphs: z.array(z.string()), datasetName: z.string(), - evaluatorConnectorId: z.string().optional(), connectorIds: z.array(z.string()), runName: z.string().optional(), alertsIndexPattern: z.string().optional().default('.alerts-security.alerts-default'), langSmithApiKey: z.string().optional(), - langSmithProject: z.string().optional(), replacements: Replacements.optional().default({}), size: z.number().optional().default(20), }); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml index 071d80156890b..d0bec37344165 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml @@ -61,8 +61,6 @@ components: type: string datasetName: type: string - evaluatorConnectorId: - type: string connectorIds: type: array items: @@ -74,8 +72,6 @@ components: default: ".alerts-security.alerts-default" langSmithApiKey: type: string - langSmithProject: - type: string replacements: $ref: "../conversations/common_attributes.schema.yaml#/components/schemas/Replacements" default: {} diff --git a/x-pack/packages/kbn-elastic-assistant-common/index.ts b/x-pack/packages/kbn-elastic-assistant-common/index.ts index 41ed86dacd9db..d8b4858d3ba8b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/index.ts @@ -25,19 +25,3 @@ export { export { transformRawData } from './impl/data_anonymization/transform_raw_data'; export { parseBedrockBuffer, handleBedrockChunk } from './impl/utils/bedrock'; export * from './constants'; - -/** currently the same shape as "fields" property in the ES response */ -export { type MaybeRawData } from './impl/alerts/helpers/types'; - -/** - * This query returns open and acknowledged (non-building block) alerts in the last 24 hours. - * - * The alerts are ordered by risk score, and then from the most recent to the oldest. - */ -export { getOpenAndAcknowledgedAlertsQuery } from './impl/alerts/get_open_and_acknowledged_alerts_query'; - -/** Returns the raw data if it valid, or a default if it's not */ -export { getRawDataOrDefault } from './impl/alerts/helpers/get_raw_data_or_default'; - -/** Return true if the provided size is out of range */ -export { sizeIsOutOfRange } from './impl/alerts/helpers/size_is_out_of_range'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx index 3b48c8d0861c5..60078178a1771 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx @@ -16,7 +16,7 @@ import * as i18n from '../../../knowledge_base/translations'; export const MIN_LATEST_ALERTS = 10; export const MAX_LATEST_ALERTS = 100; export const TICK_INTERVAL = 10; -export const RANGE_CONTAINER_WIDTH = 600; // px +export const RANGE_CONTAINER_WIDTH = 300; // px const LABEL_WRAPPER_MIN_WIDTH = 95; // px interface Props { @@ -52,7 +52,6 @@ const AlertsSettingsComponent = ({ knowledgeBase, setUpdatedKnowledgeBaseSetting <AlertsRange knowledgeBase={knowledgeBase} setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} - value={knowledgeBase.latestAlerts} /> <EuiSpacer size="s" /> </EuiFlexItem> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx index 7a3998879078d..1a6f826bd415f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx @@ -40,7 +40,6 @@ export const AlertsSettingsManagement: React.FC<Props> = React.memo( knowledgeBase={knowledgeBase} setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} compressed={false} - value={knowledgeBase.latestAlerts} /> </EuiPanel> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx index ffbcad48d1cac..cefc008eba992 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx @@ -17,34 +17,28 @@ import { EuiComboBox, EuiButton, EuiComboBoxOptionOption, - EuiComboBoxSingleSelectionShape, EuiTextColor, EuiFieldText, - EuiFieldNumber, EuiFlexItem, EuiFlexGroup, EuiLink, EuiPanel, } from '@elastic/eui'; + import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { GetEvaluateResponse, PostEvaluateRequestBodyInput, } from '@kbn/elastic-assistant-common'; -import { isEmpty } from 'lodash/fp'; - import * as i18n from './translations'; import { useAssistantContext } from '../../../assistant_context'; -import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '../../../assistant_context/constants'; import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; import { getActionTypeTitle, getGenAiConfig } from '../../../connectorland/helpers'; import { PRECONFIGURED_CONNECTOR } from '../../../connectorland/translations'; import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; import { useEvaluationData } from '../../api/evaluate/use_evaluation_data'; -const AS_PLAIN_TEXT: EuiComboBoxSingleSelectionShape = { asPlainText: true }; - /** * Evaluation Settings -- development-only feature for evaluating models */ @@ -127,18 +121,6 @@ export const EvaluationSettings: React.FC = React.memo(() => { }, [setSelectedModelOptions] ); - - const [selectedEvaluatorModel, setSelectedEvaluatorModel] = useState< - Array<EuiComboBoxOptionOption<string>> - >([]); - - const onSelectedEvaluatorModelChange = useCallback( - (selected: Array<EuiComboBoxOptionOption<string>>) => setSelectedEvaluatorModel(selected), - [] - ); - - const [size, setSize] = useState<string>(`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`); - const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); const modelOptions = useMemo(() => { return ( @@ -188,40 +170,19 @@ export const EvaluationSettings: React.FC = React.memo(() => { // Perform Evaluation Button const handlePerformEvaluation = useCallback(async () => { - const evaluatorConnectorId = - selectedEvaluatorModel[0]?.key != null - ? { evaluatorConnectorId: selectedEvaluatorModel[0].key } - : {}; - - const langSmithApiKey = isEmpty(traceOptions.langSmithApiKey) - ? undefined - : traceOptions.langSmithApiKey; - - const langSmithProject = isEmpty(traceOptions.langSmithProject) - ? undefined - : traceOptions.langSmithProject; - const evalParams: PostEvaluateRequestBodyInput = { connectorIds: selectedModelOptions.flatMap((option) => option.key ?? []).sort(), graphs: selectedGraphOptions.map((option) => option.label).sort(), datasetName: selectedDatasetOptions[0]?.label, - ...evaluatorConnectorId, - langSmithApiKey, - langSmithProject, runName, - size: Number(size), }; performEvaluation(evalParams); }, [ performEvaluation, runName, selectedDatasetOptions, - selectedEvaluatorModel, selectedGraphOptions, selectedModelOptions, - size, - traceOptions.langSmithApiKey, - traceOptions.langSmithProject, ]); const getSection = (title: string, description: string) => ( @@ -394,29 +355,6 @@ export const EvaluationSettings: React.FC = React.memo(() => { onChange={onGraphOptionsChange} /> </EuiFormRow> - - <EuiFormRow - display="rowCompressed" - helpText={i18n.EVALUATOR_MODEL_DESCRIPTION} - label={i18n.EVALUATOR_MODEL} - > - <EuiComboBox - aria-label={i18n.EVALUATOR_MODEL} - compressed - onChange={onSelectedEvaluatorModelChange} - options={modelOptions} - selectedOptions={selectedEvaluatorModel} - singleSelection={AS_PLAIN_TEXT} - /> - </EuiFormRow> - - <EuiFormRow - display="rowCompressed" - helpText={i18n.DEFAULT_MAX_ALERTS_DESCRIPTION} - label={i18n.DEFAULT_MAX_ALERTS} - > - <EuiFieldNumber onChange={(e) => setSize(e.target.value)} value={size} /> - </EuiFormRow> </EuiAccordion> <EuiHorizontalRule margin={'s'} /> <EuiFlexGroup alignItems="center"> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts index 26eddb8a223c7..62902d0f14095 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts @@ -78,36 +78,6 @@ export const CONNECTORS_LABEL = i18n.translate( } ); -export const EVALUATOR_MODEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelLabel', - { - defaultMessage: 'Evaluator model (optional)', - } -); - -export const DEFAULT_MAX_ALERTS = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.defaultMaxAlertsLabel', - { - defaultMessage: 'Default max alerts', - } -); - -export const EVALUATOR_MODEL_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelDescription', - { - defaultMessage: - 'Judge the quality of all predictions using a single model. (Default: use the same model as the connector)', - } -); - -export const DEFAULT_MAX_ALERTS_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.defaultMaxAlertsDescription', - { - defaultMessage: - 'The default maximum number of alerts to send as context, which may be overridden by the Example input', - } -); - export const CONNECTORS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx index 92a2a3df2683b..be7724d882278 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -10,9 +10,7 @@ import { KnowledgeBaseConfig } from '../assistant/types'; export const ATTACK_DISCOVERY_STORAGE_KEY = 'attackDiscovery'; export const DEFAULT_ASSISTANT_NAMESPACE = 'elasticAssistantDefault'; export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId'; -export const MAX_ALERTS_LOCAL_STORAGE_KEY = 'maxAlerts'; export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase'; -export const SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY = 'showSettingsTour'; export const STREAMING_LOCAL_STORAGE_KEY = 'streaming'; export const TRACE_OPTIONS_SESSION_STORAGE_KEY = 'traceOptions'; export const CONVERSATION_TABLE_SESSION_STORAGE_KEY = 'conversationTable'; @@ -23,9 +21,6 @@ export const ANONYMIZATION_TABLE_SESSION_STORAGE_KEY = 'anonymizationTable'; /** The default `n` latest alerts, ordered by risk score, sent as context to the assistant */ export const DEFAULT_LATEST_ALERTS = 20; -/** The default maximum number of alerts to be sent as context when generating Attack discoveries */ -export const DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS = 200; - export const DEFAULT_KNOWLEDGE_BASE_SETTINGS: KnowledgeBaseConfig = { latestAlerts: DEFAULT_LATEST_ALERTS, }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index 2319bf67de89a..c7b15f681a717 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -262,10 +262,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ docLinks, getComments, http, - knowledgeBase: { - ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, - ...localStorageKnowledgeBase, - }, + knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, ...localStorageKnowledgeBase }, promptContexts, navigateToApp, nameSpace, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx index 6cfa60eff282d..63bd86121dcc1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx @@ -7,7 +7,7 @@ import { EuiRange, useGeneratedHtmlId } from '@elastic/eui'; import { css } from '@emotion/react'; -import React, { useCallback } from 'react'; +import React from 'react'; import { MAX_LATEST_ALERTS, MIN_LATEST_ALERTS, @@ -16,57 +16,35 @@ import { import { KnowledgeBaseConfig } from '../assistant/types'; import { ALERTS_RANGE } from './translations'; -export type SingleRangeChangeEvent = - | React.ChangeEvent<HTMLInputElement> - | React.KeyboardEvent<HTMLInputElement> - | React.MouseEvent<HTMLButtonElement>; - interface Props { + knowledgeBase: KnowledgeBaseConfig; + setUpdatedKnowledgeBaseSettings: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>; compressed?: boolean; - maxAlerts?: number; - minAlerts?: number; - onChange?: (e: SingleRangeChangeEvent) => void; - knowledgeBase?: KnowledgeBaseConfig; - setUpdatedKnowledgeBaseSettings?: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>; - step?: number; - value: string | number; } const MAX_ALERTS_RANGE_WIDTH = 649; // px export const AlertsRange: React.FC<Props> = React.memo( - ({ - compressed = true, - knowledgeBase, - maxAlerts = MAX_LATEST_ALERTS, - minAlerts = MIN_LATEST_ALERTS, - onChange, - setUpdatedKnowledgeBaseSettings, - step = TICK_INTERVAL, - value, - }) => { + ({ knowledgeBase, setUpdatedKnowledgeBaseSettings, compressed = true }) => { const inputRangeSliderId = useGeneratedHtmlId({ prefix: 'inputRangeSlider' }); - const handleOnChange = useCallback( - (e: SingleRangeChangeEvent) => { - if (knowledgeBase != null && setUpdatedKnowledgeBaseSettings != null) { - setUpdatedKnowledgeBaseSettings({ - ...knowledgeBase, - latestAlerts: Number(e.currentTarget.value), - }); - } - - if (onChange != null) { - onChange(e); - } - }, - [knowledgeBase, onChange, setUpdatedKnowledgeBaseSettings] - ); - return ( <EuiRange aria-label={ALERTS_RANGE} compressed={compressed} + data-test-subj="alertsRange" + id={inputRangeSliderId} + max={MAX_LATEST_ALERTS} + min={MIN_LATEST_ALERTS} + onChange={(e) => + setUpdatedKnowledgeBaseSettings({ + ...knowledgeBase, + latestAlerts: Number(e.currentTarget.value), + }) + } + showTicks + step={TICK_INTERVAL} + value={knowledgeBase.latestAlerts} css={css` max-inline-size: ${MAX_ALERTS_RANGE_WIDTH}px; & .euiRangeTrack { @@ -74,14 +52,6 @@ export const AlertsRange: React.FC<Props> = React.memo( margin-inline-end: 0; } `} - data-test-subj="alertsRange" - id={inputRangeSliderId} - max={maxAlerts} - min={minAlerts} - onChange={handleOnChange} - showTicks - step={step} - value={value} /> ); } diff --git a/x-pack/packages/kbn-elastic-assistant/index.ts b/x-pack/packages/kbn-elastic-assistant/index.ts index 7ec65c9601268..0baff57648cc8 100644 --- a/x-pack/packages/kbn-elastic-assistant/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/index.ts @@ -77,17 +77,10 @@ export { AssistantAvatar } from './impl/assistant/assistant_avatar/assistant_ava export { ConnectorSelectorInline } from './impl/connectorland/connector_selector_inline/connector_selector_inline'; export { - /** The Attack discovery local storage key */ ATTACK_DISCOVERY_STORAGE_KEY, DEFAULT_ASSISTANT_NAMESPACE, - /** The default maximum number of alerts to be sent as context when generating Attack discoveries */ - DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, DEFAULT_LATEST_ALERTS, KNOWLEDGE_BASE_LOCAL_STORAGE_KEY, - /** The local storage key that specifies the maximum number of alerts to send as context */ - MAX_ALERTS_LOCAL_STORAGE_KEY, - /** The local storage key that specifies whether the settings tour should be shown */ - SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY, } from './impl/assistant_context/constants'; export { useLoadConnectors } from './impl/connectorland/use_load_connectors'; @@ -147,16 +140,3 @@ export { mergeBaseWithPersistedConversations } from './impl/assistant/helpers'; export { UpgradeButtons } from './impl/upgrade/upgrade_buttons'; export { getUserConversations, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; - -export { - /** A range slider component, typically used to configure the number of alerts sent as context */ - AlertsRange, - /** This event occurs when the `AlertsRange` slider is changed */ - type SingleRangeChangeEvent, -} from './impl/knowledge_base/alerts_range'; -export { - /** A label instructing the user to send fewer alerts */ - SELECT_FEWER_ALERTS, - /** Your anonymization settings will apply to these alerts (label) */ - YOUR_ANONYMIZATION_SETTINGS, -} from './impl/knowledge_base/translations'; diff --git a/x-pack/plugins/elastic_assistant/README.md b/x-pack/plugins/elastic_assistant/README.md index 8cf2c0b8903dd..2a1e47c177591 100755 --- a/x-pack/plugins/elastic_assistant/README.md +++ b/x-pack/plugins/elastic_assistant/README.md @@ -10,21 +10,15 @@ Maintained by the Security Solution team ## Graph structure -### Default Assistant graph - ![DefaultAssistantGraph](./docs/img/default_assistant_graph.png) -### Default Attack discovery graph - -![DefaultAttackDiscoveryGraph](./docs/img/default_attack_discovery_graph.png) - ## Development ### Generate graph structure To generate the graph structure, run `yarn draw-graph` from the plugin directory. -The graphs will be generated in the `docs/img` directory of the plugin. +The graph will be generated in the `docs/img` directory of the plugin. ### Testing -To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory. +To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory. \ No newline at end of file diff --git a/x-pack/plugins/elastic_assistant/docs/img/default_assistant_graph.png b/x-pack/plugins/elastic_assistant/docs/img/default_assistant_graph.png index 159b69c6d95723f08843347b2bb14d75ec2f3905..e4ef8382317e5f827778d1bb34984644f87bbf9d 100644 GIT binary patch literal 30104 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 S?F<&7j~_pA#Dr2mr~U_C`zs#+ literal 29798 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°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 diff --git a/x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png b/x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png deleted file mode 100644 index 658490900cca60fc511e729fc08e8ea5e411d60f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22551 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 X#QkvbKyG|{M>hY_zYE5pClmhyr^>}D diff --git a/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts b/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts index 3b65d307ce385..c44912ebf8d94 100644 --- a/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts +++ b/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts @@ -5,13 +5,11 @@ * 2.0. */ -import type { ElasticsearchClient } from '@kbn/core/server'; import { ToolingLog } from '@kbn/tooling-log'; import fs from 'fs/promises'; import path from 'path'; import { ActionsClientChatOpenAI, - type ActionsClientLlm, ActionsClientSimpleChatModel, } from '@kbn/langchain/server/language_models'; import type { Logger } from '@kbn/logging'; @@ -19,11 +17,6 @@ import { ChatPromptTemplate } from '@langchain/core/prompts'; import { FakeLLM } from '@langchain/core/utils/testing'; import { createOpenAIFunctionsAgent } from 'langchain/agents'; import { getDefaultAssistantGraph } from '../server/lib/langchain/graphs/default_assistant_graph/graph'; -import { getDefaultAttackDiscoveryGraph } from '../server/lib/attack_discovery/graphs/default_attack_discovery_graph'; - -interface Drawable { - drawMermaidPng: () => Promise<Blob>; -} // Just defining some test variables to get the graph to compile.. const testPrompt = ChatPromptTemplate.fromMessages([ @@ -41,7 +34,7 @@ const createLlmInstance = () => { return mockLlm; }; -async function getAssistantGraph(logger: Logger): Promise<Drawable> { +async function getGraph(logger: Logger) { const agentRunnable = await createOpenAIFunctionsAgent({ llm: mockLlm, tools: [], @@ -58,49 +51,16 @@ async function getAssistantGraph(logger: Logger): Promise<Drawable> { return graph.getGraph(); } -async function getAttackDiscoveryGraph(logger: Logger): Promise<Drawable> { - const mockEsClient = {} as unknown as ElasticsearchClient; - - const graph = getDefaultAttackDiscoveryGraph({ - anonymizationFields: [], - esClient: mockEsClient, - llm: mockLlm as unknown as ActionsClientLlm, - logger, - replacements: {}, - size: 20, - }); - - return graph.getGraph(); -} - -export const drawGraph = async ({ - getGraph, - outputFilename, -}: { - getGraph: (logger: Logger) => Promise<Drawable>; - outputFilename: string; -}) => { +export const draw = async () => { const logger = new ToolingLog({ level: 'info', writeTo: process.stdout, }) as unknown as Logger; logger.info('Compiling graph'); - const outputPath = path.join(__dirname, outputFilename); + const outputPath = path.join(__dirname, '../docs/img/default_assistant_graph.png'); const graph = await getGraph(logger); const output = await graph.drawMermaidPng(); const buffer = Buffer.from(await output.arrayBuffer()); logger.info(`Writing graph to ${outputPath}`); await fs.writeFile(outputPath, buffer); }; - -export const draw = async () => { - await drawGraph({ - getGraph: getAssistantGraph, - outputFilename: '../docs/img/default_assistant_graph.png', - }); - - await drawGraph({ - getGraph: getAttackDiscoveryGraph, - outputFilename: '../docs/img/default_attack_discovery_graph.png', - }); -}; diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts index ee54e9c451ea2..9e8a0b5d2ac90 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts @@ -6,7 +6,7 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { EsAttackDiscoverySchema } from '../lib/attack_discovery/persistence/types'; +import { EsAttackDiscoverySchema } from '../ai_assistant_data_clients/attack_discovery/types'; export const getAttackDiscoverySearchEsMock = () => { const searchResponse: estypes.SearchResponse<EsAttackDiscoverySchema> = { diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts index 473965a835f14..7e20e292a9868 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts @@ -8,7 +8,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; import { AIAssistantDataClient } from '../ai_assistant_data_clients'; -import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; +import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; type ConversationsDataClientContract = PublicMethodsOf<AIAssistantConversationsDataClient>; export type ConversationsDataClientMock = jest.Mocked<ConversationsDataClientContract>; diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts index d53ceaa586975..b52e7db536a3d 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts @@ -26,7 +26,7 @@ import { GetAIAssistantKnowledgeBaseDataClientParams, } from '../ai_assistant_data_clients/knowledge_base'; import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; -import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; +import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts index ae736c77c30ef..def0a81acea37 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts @@ -16,7 +16,7 @@ import { getPromptsSearchEsMock } from './prompts_schema.mock'; import { EsAnonymizationFieldsSchema } from '../ai_assistant_data_clients/anonymization_fields/types'; import { getAnonymizationFieldsSearchEsMock } from './anonymization_fields_schema.mock'; import { getAttackDiscoverySearchEsMock } from './attack_discovery_schema.mock'; -import { EsAttackDiscoverySchema } from '../lib/attack_discovery/persistence/types'; +import { EsAttackDiscoverySchema } from '../ai_assistant_data_clients/attack_discovery/types'; export const responseMock = { create: httpServerMock.createResponseFactory, diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts similarity index 94% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts index a82ec24c7041e..6e9cc39597bd7 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts @@ -10,11 +10,11 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { createAttackDiscovery } from './create_attack_discovery'; import { AttackDiscoveryCreateProps, AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; +import { getAttackDiscovery } from './get_attack_discovery'; import { loggerMock } from '@kbn/logging-mocks'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); -jest.mock('../get_attack_discovery/get_attack_discovery'); +jest.mock('./get_attack_discovery'); const attackDiscoveryCreate: AttackDiscoveryCreateProps = { attackDiscoveries: [], apiConfig: { diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts index fc511dc559d30..7304ab3488529 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts @@ -9,8 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryCreateProps, AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; -import { CreateAttackDiscoverySchema } from '../types'; +import { getAttackDiscovery } from './get_attack_discovery'; +import { CreateAttackDiscoverySchema } from './types'; export interface CreateAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/field_maps_configuration.ts similarity index 100% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/field_maps_configuration.ts diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts index 945603b517938..e80d1e4589838 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts @@ -8,8 +8,8 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { EsAttackDiscoverySchema } from '../types'; -import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; +import { EsAttackDiscoverySchema } from './types'; +import { transformESSearchToAttackDiscovery } from './transforms'; const MAX_ITEMS = 10000; export interface FindAllAttackDiscoveriesParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts index 53d74e6e92f42..10688ce25b25e 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts index 07fde44080026..532c35ac89c05 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts @@ -7,8 +7,8 @@ import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from '../types'; -import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; +import { EsAttackDiscoverySchema } from './types'; +import { transformESSearchToAttackDiscovery } from './transforms'; export interface FindAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts index af1a1827cbddd..4ee89fb7a3bc0 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts @@ -8,7 +8,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { getAttackDiscovery } from './get_attack_discovery'; -import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; import { AuthenticatedUser } from '@kbn/core-security-common'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts index ae2051d9e480b..d0cf6fd19ae05 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts @@ -7,8 +7,8 @@ import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from '../types'; -import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; +import { EsAttackDiscoverySchema } from './types'; +import { transformESSearchToAttackDiscovery } from './transforms'; export interface GetAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts index 5aac100f5f52c..ca053743c8035 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts @@ -11,15 +11,12 @@ import { AttackDiscoveryResponse, } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { findAllAttackDiscoveries } from './find_all_attack_discoveries/find_all_attack_discoveries'; -import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id'; -import { updateAttackDiscovery } from './update_attack_discovery/update_attack_discovery'; -import { createAttackDiscovery } from './create_attack_discovery/create_attack_discovery'; -import { getAttackDiscovery } from './get_attack_discovery/get_attack_discovery'; -import { - AIAssistantDataClient, - AIAssistantDataClientParams, -} from '../../../ai_assistant_data_clients'; +import { findAllAttackDiscoveries } from './find_all_attack_discoveries'; +import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id'; +import { updateAttackDiscovery } from './update_attack_discovery'; +import { createAttackDiscovery } from './create_attack_discovery'; +import { getAttackDiscovery } from './get_attack_discovery'; +import { AIAssistantDataClient, AIAssistantDataClientParams } from '..'; type AttackDiscoveryDataClientParams = AIAssistantDataClientParams; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts similarity index 98% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts index 765d40f7a3226..d9a37582f48b0 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts @@ -7,7 +7,7 @@ import { estypes } from '@elastic/elasticsearch'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from '../types'; +import { EsAttackDiscoverySchema } from './types'; export const transformESSearchToAttackDiscovery = ( response: estypes.SearchResponse<EsAttackDiscoverySchema> diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts index 08be262fede5a..4a17c50e06af4 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts @@ -6,7 +6,7 @@ */ import { AttackDiscoveryStatus, Provider } from '@kbn/elastic-assistant-common'; -import { EsReplacementSchema } from '../../../ai_assistant_data_clients/conversations/types'; +import { EsReplacementSchema } from '../conversations/types'; export interface EsAttackDiscoverySchema { '@timestamp': string; @@ -53,7 +53,7 @@ export interface CreateAttackDiscoverySchema { title: string; timestamp: string; details_markdown: string; - entity_summary_markdown?: string; + entity_summary_markdown: string; mitre_attack_tactics?: string[]; summary_markdown: string; id?: string; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts similarity index 97% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts index 8d98839c092aa..24deda445f320 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts @@ -7,7 +7,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; +import { getAttackDiscovery } from './get_attack_discovery'; import { updateAttackDiscovery } from './update_attack_discovery'; import { AttackDiscoveryResponse, @@ -15,7 +15,7 @@ import { AttackDiscoveryUpdateProps, } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -jest.mock('../get_attack_discovery/get_attack_discovery'); +jest.mock('./get_attack_discovery'); const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); const user = { diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts index c810a71c5f1a3..73a386bbb4362 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts @@ -14,8 +14,8 @@ import { UUID, } from '@kbn/elastic-assistant-common'; import * as uuid from 'uuid'; -import { EsReplacementSchema } from '../../../../ai_assistant_data_clients/conversations/types'; -import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; +import { EsReplacementSchema } from '../conversations/types'; +import { getAttackDiscovery } from './get_attack_discovery'; export interface UpdateAttackDiscoverySchema { id: UUID; @@ -25,7 +25,7 @@ export interface UpdateAttackDiscoverySchema { title: string; timestamp: string; details_markdown: string; - entity_summary_markdown?: string; + entity_summary_markdown: string; mitre_attack_tactics?: string[]; summary_markdown: string; id?: string; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts index 4cde64424ed7e..08912f41a8bbc 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts @@ -11,7 +11,7 @@ import type { AuthenticatedUser, Logger, ElasticsearchClient } from '@kbn/core/s import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import { Subject } from 'rxjs'; -import { attackDiscoveryFieldMap } from '../lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration'; +import { attackDiscoveryFieldMap } from '../ai_assistant_data_clients/attack_discovery/field_maps_configuration'; import { getDefaultAnonymizationFields } from '../../common/anonymization'; import { AssistantResourceNames, GetElser } from '../types'; import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; @@ -34,7 +34,7 @@ import { AIAssistantKnowledgeBaseDataClient, GetAIAssistantKnowledgeBaseDataClientParams, } from '../ai_assistant_data_clients/knowledge_base'; -import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; +import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; import { createGetElserId, createPipeline, pipelineExists } from './helpers'; const TOTAL_FIELDS_LIMIT = 2500; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts deleted file mode 100644 index d149b8c4cd44d..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Example } from 'langsmith/schemas'; - -export const exampleWithReplacements: Example = { - id: '5D436078-B2CF-487A-A0FA-7CB46696F54E', - created_at: '2024-10-10T23:01:19.350232+00:00', - dataset_id: '0DA3497B-B084-4105-AFC0-2D8E05DE4B7C', - modified_at: '2024-10-10T23:01:19.350232+00:00', - inputs: {}, - outputs: { - attackDiscoveries: [ - { - title: 'Critical Malware and Phishing Alerts on host e1cb3cf0-30f3-4f99-a9c8-518b955c6f90', - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - timestamp: '2024-10-10T22:59:52.749Z', - detailsMarkdown: - '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', - summaryMarkdown: - 'Critical malware and phishing alerts detected on {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} involving user {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', - mitreAttackTactics: ['Credential Access', 'Input Capture'], - entitySummaryMarkdown: - 'Critical malware and phishing alerts detected on {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} involving user {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}.', - }, - ], - replacements: { - '039c15c5-3964-43e7-a891-42fe2ceeb9ff': 'james', - '0b53f092-96dd-4282-bfb9-4f75a4530b80': 'root', - '1123bd7b-3afb-45d1-801a-108f04e7cfb7': 'SRVWIN04', - '3b9856bc-2c0d-4f1a-b9ae-32742e15ddd1': 'SRVWIN07', - '5306bcfd-2729-49e3-bdf0-678002778ccf': 'SRVWIN01', - '55af96a7-69b0-47cf-bf11-29be98a59eb0': 'SRVNIX05', - '66919fe3-16a4-4dfe-bc90-713f0b33a2ff': 'Administrator', - '9404361f-53fa-484f-adf8-24508256e70e': 'SRVWIN03', - 'e1cb3cf0-30f3-4f99-a9c8-518b955c6f90': 'SRVMAC08', - 'f59a00e2-f9c4-4069-8390-fd36ecd16918': 'SRVWIN02', - 'fc6d07da-5186-4d59-9b79-9382b0c226b3': 'SRVWIN06', - }, - }, - runs: [], -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts deleted file mode 100644 index 23c9c08ff5080..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Run } from 'langsmith/schemas'; - -export const runWithReplacements: Run = { - id: 'B7B03FEE-9AC4-4823-AEDB-F8EC20EAD5C4', - inputs: {}, - name: 'test', - outputs: { - attackDiscoveries: [ - { - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - detailsMarkdown: - '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` by the user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', - entitySummaryMarkdown: - 'The host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` and user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}` were involved in the attack.', - mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], - summaryMarkdown: - 'A series of critical malware alerts were detected on the host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` involving the user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', - title: 'Critical Malware Attack on macOS Host', - timestamp: '2024-10-11T17:55:59.702Z', - }, - ], - replacements: { - '039c15c5-3964-43e7-a891-42fe2ceeb9ff': 'james', - '0b53f092-96dd-4282-bfb9-4f75a4530b80': 'root', - '1123bd7b-3afb-45d1-801a-108f04e7cfb7': 'SRVWIN04', - '3b9856bc-2c0d-4f1a-b9ae-32742e15ddd1': 'SRVWIN07', - '5306bcfd-2729-49e3-bdf0-678002778ccf': 'SRVWIN01', - '55af96a7-69b0-47cf-bf11-29be98a59eb0': 'SRVNIX05', - '66919fe3-16a4-4dfe-bc90-713f0b33a2ff': 'Administrator', - '9404361f-53fa-484f-adf8-24508256e70e': 'SRVWIN03', - 'e1cb3cf0-30f3-4f99-a9c8-518b955c6f90': 'SRVMAC08', - 'f59a00e2-f9c4-4069-8390-fd36ecd16918': 'SRVWIN02', - 'fc6d07da-5186-4d59-9b79-9382b0c226b3': 'SRVWIN06', - }, - }, - run_type: 'evaluation', -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts deleted file mode 100644 index c6f6f09f1d9ae..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts +++ /dev/null @@ -1,911 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; - -export const DEFAULT_EVAL_ANONYMIZATION_FIELDS: AnonymizationFieldResponse[] = [ - { - id: 'Mx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: '_id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'NB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: '@timestamp', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'NR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'cloud.availability_zone', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Nh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'cloud.provider', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Nx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'cloud.region', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'OB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'destination.ip', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'OR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'dns.question.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Oh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'dns.question.type', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Ox09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'event.category', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'PB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'event.dataset', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'PR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'event.module', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Ph09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'event.outcome', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Px09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'file.Ext.original.path', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'QB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'file.hash.sha256', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'QR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'file.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Qh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'file.path', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Qx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'group.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'RB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'group.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'RR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'host.asset.criticality', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Rh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'host.name', - allowed: true, - anonymized: true, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Rx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'host.os.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'SB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'host.os.version', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'SR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'host.risk.calculated_level', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Sh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'host.risk.calculated_score_norm', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Sx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.original_time', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'TB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.risk_score', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'TR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.description', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Th09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Tx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.references', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'UB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.framework', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'UR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.tactic.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Uh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.tactic.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Ux09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.tactic.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'VB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.technique.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'VR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.technique.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Vh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.technique.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Vx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.technique.subtechnique.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'WB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.technique.subtechnique.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'WR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.rule.threat.technique.subtechnique.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Wh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.severity', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Wx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'kibana.alert.workflow_status', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'XB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'message', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'XR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'network.protocol', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Xh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.args', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Xx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.code_signature.exists', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'YB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.code_signature.signing_id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'YR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.code_signature.status', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Yh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.code_signature.subject_name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Yx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.code_signature.trusted', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ZB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.command_line', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ZR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.executable', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Zh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.exit_code', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'Zx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.Ext.memory_region.bytes_compressed_present', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'aB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.Ext.memory_region.malware_signature.all_names', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'aR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.Ext.memory_region.malware_signature.primary.matches', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ah09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.Ext.memory_region.malware_signature.primary.signature.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ax09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.Ext.token.integrity_level_name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'bB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.hash.md5', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'bR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.hash.sha1', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'bh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.hash.sha256', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'bx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'cB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.args', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'cR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.args_count', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ch09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.code_signature.exists', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'cx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.code_signature.status', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'dB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.code_signature.subject_name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'dR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.code_signature.trusted', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'dh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.command_line', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'dx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.executable', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'eB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.parent.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'eR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.pe.original_file_name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'eh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.pid', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ex09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'process.working_directory', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'fB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.feature', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'fR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.data', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'fh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.entropy', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'fx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.extension', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'gB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.metrics', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'gR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.operation', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'gh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.path', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'gx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.files.score', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'hB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'Ransomware.version', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'hR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'rule.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'hh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'rule.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'hx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'source.ip', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'iB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.framework', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'iR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.tactic.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ih09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.tactic.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'ix09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.tactic.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'jB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.technique.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'jR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.technique.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'jh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.technique.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'jx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.technique.subtechnique.id', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'kB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.technique.subtechnique.name', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'kR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'threat.technique.subtechnique.reference', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'kh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'user.asset.criticality', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'kx09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'user.domain', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'lB09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'user.name', - allowed: true, - anonymized: true, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'lR09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'user.risk.calculated_level', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, - { - id: 'lh09VpEBOiz7eA-eF2fb', - timestamp: '2024-08-15T13:32:10.073Z', - field: 'user.risk.calculated_score_norm', - allowed: true, - anonymized: false, - createdAt: '2024-08-15T13:32:10.073Z', - namespace: 'default', - }, -]; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts deleted file mode 100644 index 93d442bad5e9b..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ExampleInput, ExampleInputWithOverrides } from '.'; - -const validInput = { - attackDiscoveries: null, - attackDiscoveryPrompt: 'prompt', - anonymizedAlerts: [{ pageContent: 'content', metadata: { key: 'value' } }], - combinedGenerations: 'gen1gen2', - combinedRefinements: 'ref1ref2', - errors: ['error1', 'error2'], - generationAttempts: 1, - generations: ['gen1', 'gen2'], - hallucinationFailures: 0, - maxGenerationAttempts: 5, - maxHallucinationFailures: 2, - maxRepeatedGenerations: 3, - refinements: ['ref1', 'ref2'], - refinePrompt: 'refine prompt', - replacements: { key: 'replacement' }, - unrefinedResults: null, -}; - -describe('ExampleInput Schema', () => { - it('validates a correct ExampleInput object', () => { - expect(() => ExampleInput.parse(validInput)).not.toThrow(); - }); - - it('throws given an invalid ExampleInput', () => { - const invalidInput = { - attackDiscoveries: 'invalid', // should be an array or null - }; - - expect(() => ExampleInput.parse(invalidInput)).toThrow(); - }); - - it('removes unknown properties', () => { - const hasUnknownProperties = { - ...validInput, - unknownProperty: 'unknown', // <-- should be removed - }; - - const parsed = ExampleInput.parse(hasUnknownProperties); - - expect(parsed).not.toHaveProperty('unknownProperty'); - }); -}); - -describe('ExampleInputWithOverrides Schema', () => { - it('validates a correct ExampleInputWithOverrides object', () => { - const validInputWithOverrides = { - ...validInput, - overrides: { - attackDiscoveryPrompt: 'ad prompt override', - refinePrompt: 'refine prompt override', - }, - }; - - expect(() => ExampleInputWithOverrides.parse(validInputWithOverrides)).not.toThrow(); - }); - - it('throws when given an invalid ExampleInputWithOverrides object', () => { - const invalidInputWithOverrides = { - attackDiscoveries: null, - overrides: 'invalid', // should be an object - }; - - expect(() => ExampleInputWithOverrides.parse(invalidInputWithOverrides)).toThrow(); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts deleted file mode 100644 index 8183695fd7d2f..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; -import { z } from '@kbn/zod'; - -const Document = z.object({ - pageContent: z.string(), - metadata: z.record(z.string(), z.any()), -}); - -type Document = z.infer<typeof Document>; - -/** - * Parses the input from an example in a LangSmith dataset - */ -export const ExampleInput = z.object({ - attackDiscoveries: z.array(AttackDiscovery).nullable().optional(), - attackDiscoveryPrompt: z.string().optional(), - anonymizedAlerts: z.array(Document).optional(), - combinedGenerations: z.string().optional(), - combinedRefinements: z.string().optional(), - errors: z.array(z.string()).optional(), - generationAttempts: z.number().optional(), - generations: z.array(z.string()).optional(), - hallucinationFailures: z.number().optional(), - maxGenerationAttempts: z.number().optional(), - maxHallucinationFailures: z.number().optional(), - maxRepeatedGenerations: z.number().optional(), - refinements: z.array(z.string()).optional(), - refinePrompt: z.string().optional(), - replacements: Replacements.optional(), - unrefinedResults: z.array(AttackDiscovery).nullable().optional(), -}); - -export type ExampleInput = z.infer<typeof ExampleInput>; - -/** - * The optional overrides for an example input - */ -export const ExampleInputWithOverrides = z.intersection( - ExampleInput, - z.object({ - overrides: ExampleInput.optional(), - }) -); - -export type ExampleInputWithOverrides = z.infer<typeof ExampleInputWithOverrides>; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts deleted file mode 100644 index 8ea30103c0768..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getDefaultPromptTemplate } from '.'; - -describe('getDefaultPromptTemplate', () => { - it('returns the expected prompt template', () => { - const expectedTemplate = `Evaluate based on how well the following submission follows the specified rubric. Grade only based on the rubric and "expected response": - -[BEGIN rubric] -1. Is the submission non-empty and not null? -2. Is the submission well-formed JSON? -3. Evaluate the value of the "detailsMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "detailsMarkdown" in the submission capture the essence of the "expected response", regardless of the order in which they appear, and highlight the same incident(s)? -4. Evaluate the value of the "entitySummaryMarkdown" field of all the "attackDiscoveries" in the submission json. Does the value of "entitySummaryMarkdown" in the submission mention at least 50% the same entities as in the "expected response"? -5. Evaluate the value of the "summaryMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "summaryMarkdown" in the submission at least partially similar to that of the "expected response", regardless of the order in which they appear, and summarize the same incident(s)? -6. Evaluate the value of the "title" field of all the "attackDiscoveries" in the submission json. Are the "title" values in the submission at least partially similar to the tile(s) of the "expected response", regardless of the order in which they appear, and mention the same incident(s)? -7. Evaluate the value of the "alertIds" field of all the "attackDiscoveries" in the submission json. Do they match at least 100% of the "alertIds" in the submission? -[END rubric] - -[BEGIN DATA] -{input} -[BEGIN submission] -{output} -[END submission] -[BEGIN expected response] -{reference} -[END expected response] -[END DATA] - -{criteria} Base your answer based on all the grading rubric items. If at least 5 of the 7 rubric items are correct, consider the submission correct. Write out your explanation for each criterion in the rubric, first in detail, then as a separate summary on a new line. - -Then finally respond with a single character, 'Y' or 'N', on a new line without any preceding or following characters. It's important that only a single character appears on the last line.`; - - const result = getDefaultPromptTemplate(); - - expect(result).toBe(expectedTemplate); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts deleted file mode 100644 index 08e10f00e7f77..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getDefaultPromptTemplate = - () => `Evaluate based on how well the following submission follows the specified rubric. Grade only based on the rubric and "expected response": - -[BEGIN rubric] -1. Is the submission non-empty and not null? -2. Is the submission well-formed JSON? -3. Evaluate the value of the "detailsMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "detailsMarkdown" in the submission capture the essence of the "expected response", regardless of the order in which they appear, and highlight the same incident(s)? -4. Evaluate the value of the "entitySummaryMarkdown" field of all the "attackDiscoveries" in the submission json. Does the value of "entitySummaryMarkdown" in the submission mention at least 50% the same entities as in the "expected response"? -5. Evaluate the value of the "summaryMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "summaryMarkdown" in the submission at least partially similar to that of the "expected response", regardless of the order in which they appear, and summarize the same incident(s)? -6. Evaluate the value of the "title" field of all the "attackDiscoveries" in the submission json. Are the "title" values in the submission at least partially similar to the tile(s) of the "expected response", regardless of the order in which they appear, and mention the same incident(s)? -7. Evaluate the value of the "alertIds" field of all the "attackDiscoveries" in the submission json. Do they match at least 100% of the "alertIds" in the submission? -[END rubric] - -[BEGIN DATA] -{input} -[BEGIN submission] -{output} -[END submission] -[BEGIN expected response] -{reference} -[END expected response] -[END DATA] - -{criteria} Base your answer based on all the grading rubric items. If at least 5 of the 7 rubric items are correct, consider the submission correct. Write out your explanation for each criterion in the rubric, first in detail, then as a separate summary on a new line. - -Then finally respond with a single character, 'Y' or 'N', on a new line without any preceding or following characters. It's important that only a single character appears on the last line.`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts deleted file mode 100644 index c261f151b99ab..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash/fp'; - -import { getExampleAttackDiscoveriesWithReplacements } from '.'; -import { exampleWithReplacements } from '../../../__mocks__/mock_examples'; - -describe('getExampleAttackDiscoveriesWithReplacements', () => { - it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { - const result = getExampleAttackDiscoveriesWithReplacements(exampleWithReplacements); - - expect(result).toEqual([ - { - title: 'Critical Malware and Phishing Alerts on host SRVMAC08', - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - timestamp: '2024-10-10T22:59:52.749Z', - detailsMarkdown: - '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name SRVMAC08 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name james }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', - summaryMarkdown: - 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', - mitreAttackTactics: ['Credential Access', 'Input Capture'], - entitySummaryMarkdown: - 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}.', - }, - ]); - }); - - it('returns an empty entitySummaryMarkdown when the entitySummaryMarkdown is missing', () => { - const missingEntitySummaryMarkdown = omit( - 'entitySummaryMarkdown', - exampleWithReplacements.outputs?.attackDiscoveries?.[0] - ); - - const exampleWithMissingEntitySummaryMarkdown = { - ...exampleWithReplacements, - outputs: { - ...exampleWithReplacements.outputs, - attackDiscoveries: [missingEntitySummaryMarkdown], - }, - }; - - const result = getExampleAttackDiscoveriesWithReplacements( - exampleWithMissingEntitySummaryMarkdown - ); - - expect(result).toEqual([ - { - title: 'Critical Malware and Phishing Alerts on host SRVMAC08', - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - timestamp: '2024-10-10T22:59:52.749Z', - detailsMarkdown: - '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name SRVMAC08 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name james }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', - summaryMarkdown: - 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', - mitreAttackTactics: ['Credential Access', 'Input Capture'], - entitySummaryMarkdown: '', - }, - ]); - }); - - it('throws when an example is undefined', () => { - expect(() => getExampleAttackDiscoveriesWithReplacements(undefined)).toThrowError(); - }); - - it('throws when the example is missing attackDiscoveries', () => { - const missingAttackDiscoveries = { - ...exampleWithReplacements, - outputs: { - replacements: { ...exampleWithReplacements.outputs?.replacements }, - }, - }; - - expect(() => - getExampleAttackDiscoveriesWithReplacements(missingAttackDiscoveries) - ).toThrowError(); - }); - - it('throws when attackDiscoveries is null', () => { - const nullAttackDiscoveries = { - ...exampleWithReplacements, - outputs: { - attackDiscoveries: null, - replacements: { ...exampleWithReplacements.outputs?.replacements }, - }, - }; - - expect(() => getExampleAttackDiscoveriesWithReplacements(nullAttackDiscoveries)).toThrowError(); - }); - - it('returns the original attack discoveries when replacements are missing', () => { - const missingReplacements = { - ...exampleWithReplacements, - outputs: { - attackDiscoveries: [...exampleWithReplacements.outputs?.attackDiscoveries], - }, - }; - - const result = getExampleAttackDiscoveriesWithReplacements(missingReplacements); - - expect(result).toEqual(exampleWithReplacements.outputs?.attackDiscoveries); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts deleted file mode 100644 index 8fc5de2a08ed1..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; -import type { Example } from 'langsmith/schemas'; - -import { getDiscoveriesWithOriginalValues } from '../../get_discoveries_with_original_values'; - -export const getExampleAttackDiscoveriesWithReplacements = ( - example: Example | undefined -): AttackDiscoveries => { - const exampleAttackDiscoveries = example?.outputs?.attackDiscoveries; - const exampleReplacements = example?.outputs?.replacements ?? {}; - - // NOTE: calls to `parse` throw an error if the Example input is invalid - const validatedAttackDiscoveries = AttackDiscoveries.parse(exampleAttackDiscoveries); - const validatedReplacements = Replacements.parse(exampleReplacements); - - const withReplacements = getDiscoveriesWithOriginalValues({ - attackDiscoveries: validatedAttackDiscoveries, - replacements: validatedReplacements, - }); - - return withReplacements; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts deleted file mode 100644 index bd22e5d952b07..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash/fp'; - -import { getRunAttackDiscoveriesWithReplacements } from '.'; -import { runWithReplacements } from '../../../__mocks__/mock_runs'; - -describe('getRunAttackDiscoveriesWithReplacements', () => { - it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { - const result = getRunAttackDiscoveriesWithReplacements(runWithReplacements); - - expect(result).toEqual([ - { - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - detailsMarkdown: - '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', - entitySummaryMarkdown: - 'The host `{{ host.name SRVMAC08 }}` and user `{{ user.name james }}` were involved in the attack.', - mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], - summaryMarkdown: - 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', - title: 'Critical Malware Attack on macOS Host', - timestamp: '2024-10-11T17:55:59.702Z', - }, - ]); - }); - - it("returns an empty entitySummaryMarkdown when it's missing from the attack discovery", () => { - const missingEntitySummaryMarkdown = omit( - 'entitySummaryMarkdown', - runWithReplacements.outputs?.attackDiscoveries?.[0] - ); - - const runWithMissingEntitySummaryMarkdown = { - ...runWithReplacements, - outputs: { - ...runWithReplacements.outputs, - attackDiscoveries: [missingEntitySummaryMarkdown], - }, - }; - - const result = getRunAttackDiscoveriesWithReplacements(runWithMissingEntitySummaryMarkdown); - - expect(result).toEqual([ - { - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - detailsMarkdown: - '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', - entitySummaryMarkdown: '', - mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], - summaryMarkdown: - 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', - title: 'Critical Malware Attack on macOS Host', - timestamp: '2024-10-11T17:55:59.702Z', - }, - ]); - }); - - it('throws when the run is missing attackDiscoveries', () => { - const missingAttackDiscoveries = { - ...runWithReplacements, - outputs: { - replacements: { ...runWithReplacements.outputs?.replacements }, - }, - }; - - expect(() => getRunAttackDiscoveriesWithReplacements(missingAttackDiscoveries)).toThrowError(); - }); - - it('throws when attackDiscoveries is null', () => { - const nullAttackDiscoveries = { - ...runWithReplacements, - outputs: { - attackDiscoveries: null, - replacements: { ...runWithReplacements.outputs?.replacements }, - }, - }; - - expect(() => getRunAttackDiscoveriesWithReplacements(nullAttackDiscoveries)).toThrowError(); - }); - - it('returns the original attack discoveries when replacements are missing', () => { - const missingReplacements = { - ...runWithReplacements, - outputs: { - attackDiscoveries: [...runWithReplacements.outputs?.attackDiscoveries], - }, - }; - - const result = getRunAttackDiscoveriesWithReplacements(missingReplacements); - - expect(result).toEqual(runWithReplacements.outputs?.attackDiscoveries); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts deleted file mode 100644 index 01193320f712b..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; -import type { Run } from 'langsmith/schemas'; - -import { getDiscoveriesWithOriginalValues } from '../../get_discoveries_with_original_values'; - -export const getRunAttackDiscoveriesWithReplacements = (run: Run): AttackDiscoveries => { - const runAttackDiscoveries = run.outputs?.attackDiscoveries; - const runReplacements = run.outputs?.replacements ?? {}; - - // NOTE: calls to `parse` throw an error if the Run Input is invalid - const validatedAttackDiscoveries = AttackDiscoveries.parse(runAttackDiscoveries); - const validatedReplacements = Replacements.parse(runReplacements); - - const withReplacements = getDiscoveriesWithOriginalValues({ - attackDiscoveries: validatedAttackDiscoveries, - replacements: validatedReplacements, - }); - - return withReplacements; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts deleted file mode 100644 index 829e27df73f14..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { PromptTemplate } from '@langchain/core/prompts'; -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import { loadEvaluator } from 'langchain/evaluation'; - -import { type GetCustomEvaluatorOptions, getCustomEvaluator } from '.'; -import { getDefaultPromptTemplate } from './get_default_prompt_template'; -import { getExampleAttackDiscoveriesWithReplacements } from './get_example_attack_discoveries_with_replacements'; -import { getRunAttackDiscoveriesWithReplacements } from './get_run_attack_discoveries_with_replacements'; -import { exampleWithReplacements } from '../../__mocks__/mock_examples'; -import { runWithReplacements } from '../../__mocks__/mock_runs'; - -const mockLlm = jest.fn() as unknown as ActionsClientLlm; - -jest.mock('langchain/evaluation', () => ({ - ...jest.requireActual('langchain/evaluation'), - loadEvaluator: jest.fn().mockResolvedValue({ - evaluateStrings: jest.fn().mockResolvedValue({ - key: 'correctness', - score: 0.9, - }), - }), -})); - -const options: GetCustomEvaluatorOptions = { - criteria: 'correctness', - key: 'attack_discovery_correctness', - llm: mockLlm, - template: getDefaultPromptTemplate(), -}; - -describe('getCustomEvaluator', () => { - beforeEach(() => jest.clearAllMocks()); - - it('returns an evaluator function', () => { - const evaluator = getCustomEvaluator(options); - - expect(typeof evaluator).toBe('function'); - }); - - it('calls loadEvaluator with the expected arguments', async () => { - const evaluator = getCustomEvaluator(options); - - await evaluator(runWithReplacements, exampleWithReplacements); - - expect(loadEvaluator).toHaveBeenCalledWith('labeled_criteria', { - criteria: options.criteria, - chainOptions: { - prompt: PromptTemplate.fromTemplate(options.template), - }, - llm: mockLlm, - }); - }); - - it('calls evaluateStrings with the expected arguments', async () => { - const mockEvaluateStrings = jest.fn().mockResolvedValue({ - key: 'correctness', - score: 0.9, - }); - - (loadEvaluator as jest.Mock).mockResolvedValue({ - evaluateStrings: mockEvaluateStrings, - }); - - const evaluator = getCustomEvaluator(options); - - await evaluator(runWithReplacements, exampleWithReplacements); - - const prediction = getRunAttackDiscoveriesWithReplacements(runWithReplacements); - const reference = getExampleAttackDiscoveriesWithReplacements(exampleWithReplacements); - - expect(mockEvaluateStrings).toHaveBeenCalledWith({ - input: '', - prediction: JSON.stringify(prediction, null, 2), - reference: JSON.stringify(reference, null, 2), - }); - }); - - it('returns the expected result', async () => { - const evaluator = getCustomEvaluator(options); - - const result = await evaluator(runWithReplacements, exampleWithReplacements); - - expect(result).toEqual({ key: 'attack_discovery_correctness', score: 0.9 }); - }); - - it('throws given an undefined example', async () => { - const evaluator = getCustomEvaluator(options); - - await expect(async () => evaluator(runWithReplacements, undefined)).rejects.toThrow(); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts deleted file mode 100644 index bcabe410c1b72..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import { PromptTemplate } from '@langchain/core/prompts'; -import type { EvaluationResult } from 'langsmith/evaluation'; -import type { Run, Example } from 'langsmith/schemas'; -import { CriteriaLike, loadEvaluator } from 'langchain/evaluation'; - -import { getExampleAttackDiscoveriesWithReplacements } from './get_example_attack_discoveries_with_replacements'; -import { getRunAttackDiscoveriesWithReplacements } from './get_run_attack_discoveries_with_replacements'; - -export interface GetCustomEvaluatorOptions { - /** - * Examples: - * - "conciseness" - * - "relevance" - * - "correctness" - * - "detail" - */ - criteria: CriteriaLike; - /** - * The evaluation score will use this key - */ - key: string; - /** - * LLm to use for evaluation - */ - llm: ActionsClientLlm; - /** - * A prompt template that uses the {input}, {submission}, and {reference} variables - */ - template: string; -} - -export type CustomEvaluator = ( - rootRun: Run, - example: Example | undefined -) => Promise<EvaluationResult>; - -export const getCustomEvaluator = - ({ criteria, key, llm, template }: GetCustomEvaluatorOptions): CustomEvaluator => - async (rootRun, example) => { - const chain = await loadEvaluator('labeled_criteria', { - criteria, - chainOptions: { - prompt: PromptTemplate.fromTemplate(template), - }, - llm, - }); - - const exampleAttackDiscoveriesWithReplacements = - getExampleAttackDiscoveriesWithReplacements(example); - - const runAttackDiscoveriesWithReplacements = getRunAttackDiscoveriesWithReplacements(rootRun); - - // NOTE: res contains a score, as well as the reasoning for the score - const res = await chain.evaluateStrings({ - input: '', // empty for now, but this could be the alerts, i.e. JSON.stringify(rootRun.outputs?.anonymizedAlerts, null, 2), - prediction: JSON.stringify(runAttackDiscoveriesWithReplacements, null, 2), - reference: JSON.stringify(exampleAttackDiscoveriesWithReplacements, null, 2), - }); - - return { key, score: res.score }; - }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts deleted file mode 100644 index 423248aa5c3d6..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscovery } from '@kbn/elastic-assistant-common'; -import { omit } from 'lodash/fp'; - -import { getDiscoveriesWithOriginalValues } from '.'; -import { runWithReplacements } from '../../__mocks__/mock_runs'; - -describe('getDiscoveriesWithOriginalValues', () => { - it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { - const result = getDiscoveriesWithOriginalValues({ - attackDiscoveries: runWithReplacements.outputs?.attackDiscoveries, - replacements: runWithReplacements.outputs?.replacements, - }); - - expect(result).toEqual([ - { - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - detailsMarkdown: - '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', - entitySummaryMarkdown: - 'The host `{{ host.name SRVMAC08 }}` and user `{{ user.name james }}` were involved in the attack.', - mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], - summaryMarkdown: - 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', - title: 'Critical Malware Attack on macOS Host', - timestamp: '2024-10-11T17:55:59.702Z', - }, - ]); - }); - - it("returns an empty entitySummaryMarkdown when it's missing from the attack discovery", () => { - const missingEntitySummaryMarkdown = omit( - 'entitySummaryMarkdown', - runWithReplacements.outputs?.attackDiscoveries?.[0] - ) as unknown as AttackDiscovery; - - const result = getDiscoveriesWithOriginalValues({ - attackDiscoveries: [missingEntitySummaryMarkdown], - replacements: runWithReplacements.outputs?.replacements, - }); - expect(result).toEqual([ - { - alertIds: [ - '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', - 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', - '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', - '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', - 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', - '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', - '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', - '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', - ], - detailsMarkdown: - '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', - entitySummaryMarkdown: '', - mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], - summaryMarkdown: - 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', - title: 'Critical Malware Attack on macOS Host', - timestamp: '2024-10-11T17:55:59.702Z', - }, - ]); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts deleted file mode 100644 index 1ef88e2208d1f..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - type AttackDiscovery, - Replacements, - replaceAnonymizedValuesWithOriginalValues, -} from '@kbn/elastic-assistant-common'; - -export const getDiscoveriesWithOriginalValues = ({ - attackDiscoveries, - replacements, -}: { - attackDiscoveries: AttackDiscovery[]; - replacements: Replacements; -}): AttackDiscovery[] => - attackDiscoveries.map((attackDiscovery) => ({ - ...attackDiscovery, - detailsMarkdown: replaceAnonymizedValuesWithOriginalValues({ - messageContent: attackDiscovery.detailsMarkdown, - replacements, - }), - entitySummaryMarkdown: replaceAnonymizedValuesWithOriginalValues({ - messageContent: attackDiscovery.entitySummaryMarkdown ?? '', - replacements, - }), - summaryMarkdown: replaceAnonymizedValuesWithOriginalValues({ - messageContent: attackDiscovery.summaryMarkdown, - replacements, - }), - title: replaceAnonymizedValuesWithOriginalValues({ - messageContent: attackDiscovery.title, - replacements, - }), - })); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts deleted file mode 100644 index 132a819d44ec8..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClient } from '@kbn/actions-plugin/server'; -import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; -import { ActionsClientLlm } from '@kbn/langchain/server'; -import { loggerMock } from '@kbn/logging-mocks'; - -import { getEvaluatorLlm } from '.'; - -jest.mock('@kbn/langchain/server', () => ({ - ...jest.requireActual('@kbn/langchain/server'), - - ActionsClientLlm: jest.fn(), -})); - -const connectorTimeout = 1000; - -const evaluatorConnectorId = 'evaluator-connector-id'; -const evaluatorConnector = { - id: 'evaluatorConnectorId', - actionTypeId: '.gen-ai', - name: 'GPT-4o', - isPreconfigured: true, - isSystemAction: false, - isDeprecated: false, -} as Connector; - -const experimentConnector: Connector = { - name: 'Gemini 1.5 Pro 002', - actionTypeId: '.gemini', - config: { - apiUrl: 'https://example.com', - defaultModel: 'gemini-1.5-pro-002', - gcpRegion: 'test-region', - gcpProjectID: 'test-project-id', - }, - secrets: { - credentialsJson: '{}', - }, - id: 'gemini-1-5-pro-002', - isPreconfigured: true, - isSystemAction: false, - isDeprecated: false, -} as Connector; - -const logger = loggerMock.create(); - -describe('getEvaluatorLlm', () => { - beforeEach(() => jest.clearAllMocks()); - - describe('getting the evaluation connector', () => { - it("calls actionsClient.get with the evaluator connector ID when it's provided", async () => { - const actionsClient = { - get: jest.fn(), - } as unknown as ActionsClient; - - await getEvaluatorLlm({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - experimentConnector, - langSmithApiKey: undefined, - logger, - }); - - expect(actionsClient.get).toHaveBeenCalledWith({ - id: evaluatorConnectorId, - throwIfSystemAction: false, - }); - }); - - it("calls actionsClient.get with the experiment connector ID when the evaluator connector ID isn't provided", async () => { - const actionsClient = { - get: jest.fn().mockResolvedValue(null), - } as unknown as ActionsClient; - - await getEvaluatorLlm({ - actionsClient, - connectorTimeout, - evaluatorConnectorId: undefined, - experimentConnector, - langSmithApiKey: undefined, - logger, - }); - - expect(actionsClient.get).toHaveBeenCalledWith({ - id: experimentConnector.id, - throwIfSystemAction: false, - }); - }); - - it('falls back to the experiment connector when the evaluator connector is not found', async () => { - const actionsClient = { - get: jest.fn().mockResolvedValue(null), - } as unknown as ActionsClient; - - await getEvaluatorLlm({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - experimentConnector, - langSmithApiKey: undefined, - logger, - }); - - expect(ActionsClientLlm).toHaveBeenCalledWith( - expect.objectContaining({ - connectorId: experimentConnector.id, - }) - ); - }); - }); - - it('logs the expected connector names and types', async () => { - const actionsClient = { - get: jest.fn().mockResolvedValue(evaluatorConnector), - } as unknown as ActionsClient; - - await getEvaluatorLlm({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - experimentConnector, - langSmithApiKey: undefined, - logger, - }); - - expect(logger.info).toHaveBeenCalledWith( - `The ${evaluatorConnector.name} (openai) connector will judge output from the ${experimentConnector.name} (gemini) connector` - ); - }); - - it('creates a new ActionsClientLlm instance with the expected traceOptions', async () => { - const actionsClient = { - get: jest.fn().mockResolvedValue(evaluatorConnector), - } as unknown as ActionsClient; - - await getEvaluatorLlm({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - experimentConnector, - langSmithApiKey: 'test-api-key', - logger, - }); - - expect(ActionsClientLlm).toHaveBeenCalledWith( - expect.objectContaining({ - traceOptions: { - projectName: 'evaluators', - tracers: expect.any(Array), - }, - }) - ); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts deleted file mode 100644 index 236def9670d07..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClient } from '@kbn/actions-plugin/server'; -import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; -import { Logger } from '@kbn/core/server'; -import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; -import { ActionsClientLlm } from '@kbn/langchain/server'; -import { PublicMethodsOf } from '@kbn/utility-types'; - -import { getLlmType } from '../../../../../routes/utils'; - -export const getEvaluatorLlm = async ({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - experimentConnector, - langSmithApiKey, - logger, -}: { - actionsClient: PublicMethodsOf<ActionsClient>; - connectorTimeout: number; - evaluatorConnectorId: string | undefined; - experimentConnector: Connector; - langSmithApiKey: string | undefined; - logger: Logger; -}): Promise<ActionsClientLlm> => { - const evaluatorConnector = - (await actionsClient.get({ - id: evaluatorConnectorId ?? experimentConnector.id, // fallback to the experiment connector if the evaluator connector is not found: - throwIfSystemAction: false, - })) ?? experimentConnector; - - const evaluatorLlmType = getLlmType(evaluatorConnector.actionTypeId); - const experimentLlmType = getLlmType(experimentConnector.actionTypeId); - - logger.info( - `The ${evaluatorConnector.name} (${evaluatorLlmType}) connector will judge output from the ${experimentConnector.name} (${experimentLlmType}) connector` - ); - - const traceOptions = { - projectName: 'evaluators', - tracers: [ - ...getLangSmithTracer({ - apiKey: langSmithApiKey, - projectName: 'evaluators', - logger, - }), - ], - }; - - return new ActionsClientLlm({ - actionsClient, - connectorId: evaluatorConnector.id, - llmType: evaluatorLlmType, - logger, - temperature: 0, // zero temperature for evaluation - timeout: connectorTimeout, - traceOptions, - }); -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts deleted file mode 100644 index 47f36bc6fb0e7..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash/fp'; -import type { Example } from 'langsmith/schemas'; - -import { getGraphInputOverrides } from '.'; -import { exampleWithReplacements } from '../../__mocks__/mock_examples'; - -const exampleWithAlerts: Example = { - ...exampleWithReplacements, - outputs: { - ...exampleWithReplacements.outputs, - anonymizedAlerts: [ - { - metadata: {}, - pageContent: - '@timestamp,2024-10-10T21:01:24.148Z\n' + - '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + - 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + - 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', - }, - { - metadata: {}, - pageContent: - '@timestamp,2024-10-10T21:01:24.148Z\n' + - '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + - 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + - 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', - }, - ], - }, -}; - -const exampleWithNoReplacements: Example = { - ...exampleWithReplacements, - outputs: { - ...omit('replacements', exampleWithReplacements.outputs), - }, -}; - -describe('getGraphInputOverrides', () => { - describe('root-level outputs overrides', () => { - it('returns the anonymizedAlerts from the root level of the outputs when present', () => { - const overrides = getGraphInputOverrides(exampleWithAlerts.outputs); - - expect(overrides.anonymizedAlerts).toEqual(exampleWithAlerts.outputs?.anonymizedAlerts); - }); - - it('does NOT populate the anonymizedAlerts key when it does NOT exist in the outputs', () => { - const overrides = getGraphInputOverrides(exampleWithReplacements.outputs); - - expect(overrides).not.toHaveProperty('anonymizedAlerts'); - }); - - it('returns replacements from the root level of the outputs when present', () => { - const overrides = getGraphInputOverrides(exampleWithReplacements.outputs); - - expect(overrides.replacements).toEqual(exampleWithReplacements.outputs?.replacements); - }); - - it('does NOT populate the replacements key when it does NOT exist in the outputs', () => { - const overrides = getGraphInputOverrides(exampleWithNoReplacements.outputs); - - expect(overrides).not.toHaveProperty('replacements'); - }); - - it('removes unknown properties', () => { - const withUnknownProperties = { - ...exampleWithReplacements, - outputs: { - ...exampleWithReplacements.outputs, - unknownProperty: 'unknown', - }, - }; - - const overrides = getGraphInputOverrides(withUnknownProperties.outputs); - - expect(overrides).not.toHaveProperty('unknownProperty'); - }); - }); - - describe('overrides', () => { - it('returns all overrides at the root level', () => { - const exampleWithOverrides = { - ...exampleWithAlerts, - outputs: { - ...exampleWithAlerts.outputs, - overrides: { - attackDiscoveries: [], - attackDiscoveryPrompt: 'prompt', - anonymizedAlerts: [], - combinedGenerations: 'combinedGenerations', - combinedRefinements: 'combinedRefinements', - errors: ['error'], - generationAttempts: 1, - generations: ['generation'], - hallucinationFailures: 2, - maxGenerationAttempts: 3, - maxHallucinationFailures: 4, - maxRepeatedGenerations: 5, - refinements: ['refinement'], - refinePrompt: 'refinePrompt', - replacements: {}, - unrefinedResults: [], - }, - }, - }; - - const overrides = getGraphInputOverrides(exampleWithOverrides.outputs); - - expect(overrides).toEqual({ - ...exampleWithOverrides.outputs?.overrides, - }); - }); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts deleted file mode 100644 index 232218f4386f8..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { pick } from 'lodash/fp'; - -import { ExampleInputWithOverrides } from '../../example_input'; -import { GraphState } from '../../../graphs/default_attack_discovery_graph/types'; - -/** - * Parses input from an LangSmith dataset example to get the graph input overrides - */ -export const getGraphInputOverrides = (outputs: unknown): Partial<GraphState> => { - const validatedInput = ExampleInputWithOverrides.safeParse(outputs).data ?? {}; // safeParse removes unknown properties - - const { overrides } = validatedInput; - - // return all overrides at the root level: - return { - // pick extracts just the anonymizedAlerts and replacements from the root level of the input, - // and only adds the anonymizedAlerts key if it exists in the input - ...pick('anonymizedAlerts', validatedInput), - ...pick('replacements', validatedInput), - ...overrides, // bring all other overrides to the root level - }; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts deleted file mode 100644 index 40b0f080fe54a..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClient } from '@kbn/actions-plugin/server'; -import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { Logger } from '@kbn/core/server'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; -import { ActionsClientLlm } from '@kbn/langchain/server'; -import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; -import { asyncForEach } from '@kbn/std'; -import { PublicMethodsOf } from '@kbn/utility-types'; - -import { DEFAULT_EVAL_ANONYMIZATION_FIELDS } from './constants'; -import { AttackDiscoveryGraphMetadata } from '../../langchain/graphs'; -import { DefaultAttackDiscoveryGraph } from '../graphs/default_attack_discovery_graph'; -import { getLlmType } from '../../../routes/utils'; -import { runEvaluations } from './run_evaluations'; - -export const evaluateAttackDiscovery = async ({ - actionsClient, - attackDiscoveryGraphs, - alertsIndexPattern, - anonymizationFields = DEFAULT_EVAL_ANONYMIZATION_FIELDS, // determines which fields are included in the alerts - connectors, - connectorTimeout, - datasetName, - esClient, - evaluationId, - evaluatorConnectorId, - langSmithApiKey, - langSmithProject, - logger, - runName, - size, -}: { - actionsClient: PublicMethodsOf<ActionsClient>; - attackDiscoveryGraphs: AttackDiscoveryGraphMetadata[]; - alertsIndexPattern: string; - anonymizationFields?: AnonymizationFieldResponse[]; - connectors: Connector[]; - connectorTimeout: number; - datasetName: string; - esClient: ElasticsearchClient; - evaluationId: string; - evaluatorConnectorId: string | undefined; - langSmithApiKey: string | undefined; - langSmithProject: string | undefined; - logger: Logger; - runName: string; - size: number; -}): Promise<void> => { - await asyncForEach(attackDiscoveryGraphs, async ({ getDefaultAttackDiscoveryGraph }) => { - // create a graph for every connector: - const graphs: Array<{ - connector: Connector; - graph: DefaultAttackDiscoveryGraph; - llmType: string | undefined; - name: string; - traceOptions: { - projectName: string | undefined; - tracers: LangChainTracer[]; - }; - }> = connectors.map((connector) => { - const llmType = getLlmType(connector.actionTypeId); - - const traceOptions = { - projectName: langSmithProject, - tracers: [ - ...getLangSmithTracer({ - apiKey: langSmithApiKey, - projectName: langSmithProject, - logger, - }), - ], - }; - - const llm = new ActionsClientLlm({ - actionsClient, - connectorId: connector.id, - llmType, - logger, - temperature: 0, // zero temperature for attack discovery, because we want structured JSON output - timeout: connectorTimeout, - traceOptions, - }); - - const graph = getDefaultAttackDiscoveryGraph({ - alertsIndexPattern, - anonymizationFields, - esClient, - llm, - logger, - size, - }); - - return { - connector, - graph, - llmType, - name: `${runName} - ${connector.name} - ${evaluationId} - Attack discovery`, - traceOptions, - }; - }); - - // run the evaluations for each graph: - await runEvaluations({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - datasetName, - graphs, - langSmithApiKey, - logger, - }); - }); -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts deleted file mode 100644 index 19eb99d57c84c..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClient } from '@kbn/actions-plugin/server'; -import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; -import { Logger } from '@kbn/core/server'; -import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; -import { asyncForEach } from '@kbn/std'; -import { PublicMethodsOf } from '@kbn/utility-types'; -import { Client } from 'langsmith'; -import { evaluate } from 'langsmith/evaluation'; - -import { getEvaluatorLlm } from '../helpers/get_evaluator_llm'; -import { getCustomEvaluator } from '../helpers/get_custom_evaluator'; -import { getDefaultPromptTemplate } from '../helpers/get_custom_evaluator/get_default_prompt_template'; -import { getGraphInputOverrides } from '../helpers/get_graph_input_overrides'; -import { DefaultAttackDiscoveryGraph } from '../../graphs/default_attack_discovery_graph'; -import { GraphState } from '../../graphs/default_attack_discovery_graph/types'; - -/** - * Runs an evaluation for each graph so they show up separately (resulting in - * each dataset run grouped by connector) - */ -export const runEvaluations = async ({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - datasetName, - graphs, - langSmithApiKey, - logger, -}: { - actionsClient: PublicMethodsOf<ActionsClient>; - connectorTimeout: number; - evaluatorConnectorId: string | undefined; - datasetName: string; - graphs: Array<{ - connector: Connector; - graph: DefaultAttackDiscoveryGraph; - llmType: string | undefined; - name: string; - traceOptions: { - projectName: string | undefined; - tracers: LangChainTracer[]; - }; - }>; - langSmithApiKey: string | undefined; - logger: Logger; -}): Promise<void> => - asyncForEach(graphs, async ({ connector, graph, llmType, name, traceOptions }) => { - const subject = `connector "${connector.name}" (${llmType}), running experiment "${name}"`; - - try { - logger.info( - () => - `Evaluating ${subject} with dataset "${datasetName}" and evaluator "${evaluatorConnectorId}"` - ); - - const predict = async (input: unknown): Promise<GraphState> => { - logger.debug(() => `Raw example Input for ${subject}":\n ${input}`); - - // The example `Input` may have overrides for the initial state of the graph: - const overrides = getGraphInputOverrides(input); - - return graph.invoke( - { - ...overrides, - }, - { - callbacks: [...(traceOptions.tracers ?? [])], - runName: name, - tags: ['evaluation', llmType ?? ''], - } - ); - }; - - const llm = await getEvaluatorLlm({ - actionsClient, - connectorTimeout, - evaluatorConnectorId, - experimentConnector: connector, - langSmithApiKey, - logger, - }); - - const customEvaluator = getCustomEvaluator({ - criteria: 'correctness', - key: 'attack_discovery_correctness', - llm, - template: getDefaultPromptTemplate(), - }); - - const evalOutput = await evaluate(predict, { - client: new Client({ apiKey: langSmithApiKey }), - data: datasetName ?? '', - evaluators: [customEvaluator], - experimentPrefix: name, - maxConcurrency: 5, // prevents rate limiting - }); - - logger.info(() => `Evaluation complete for ${subject}`); - - logger.debug( - () => `Evaluation output for ${subject}:\n ${JSON.stringify(evalOutput, null, 2)}` - ); - } catch (e) { - logger.error(`Error evaluating ${subject}: ${e}`); - } - }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts deleted file mode 100644 index fb5df8f26d0c2..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// LangGraph metadata -export const ATTACK_DISCOVERY_GRAPH_RUN_NAME = 'Attack discovery'; -export const ATTACK_DISCOVERY_TAG = 'attack-discovery'; - -// Limits -export const DEFAULT_MAX_GENERATION_ATTEMPTS = 10; -export const DEFAULT_MAX_HALLUCINATION_FAILURES = 5; -export const DEFAULT_MAX_REPEATED_GENERATIONS = 3; - -export const NodeType = { - GENERATE_NODE: 'generate', - REFINE_NODE: 'refine', - RETRIEVE_ANONYMIZED_ALERTS_NODE: 'retrieve_anonymized_alerts', -} as const; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts deleted file mode 100644 index 225c4a2b8935c..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getGenerateOrEndDecision } from '.'; - -describe('getGenerateOrEndDecision', () => { - it('returns "end" when hasZeroAlerts is true', () => { - const result = getGenerateOrEndDecision(true); - - expect(result).toEqual('end'); - }); - - it('returns "generate" when hasZeroAlerts is false', () => { - const result = getGenerateOrEndDecision(false); - - expect(result).toEqual('generate'); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts deleted file mode 100644 index b134b2f3a6118..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getGenerateOrEndDecision = (hasZeroAlerts: boolean): 'end' | 'generate' => - hasZeroAlerts ? 'end' : 'generate'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts deleted file mode 100644 index 06dd1529179fa..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { loggerMock } from '@kbn/logging-mocks'; - -import { getGenerateOrEndEdge } from '.'; -import type { GraphState } from '../../types'; - -const logger = loggerMock.create(); - -const graphState: GraphState = { - attackDiscoveries: null, - attackDiscoveryPrompt: 'prompt', - anonymizedAlerts: [ - { - metadata: {}, - pageContent: - '@timestamp,2024-10-10T21:01:24.148Z\n' + - '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + - 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + - 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', - }, - { - metadata: {}, - pageContent: - '@timestamp,2024-10-10T21:01:24.148Z\n' + - '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + - 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + - 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', - }, - ], - combinedGenerations: 'generations', - combinedRefinements: 'refinements', - errors: [], - generationAttempts: 0, - generations: [], - hallucinationFailures: 0, - maxGenerationAttempts: 10, - maxHallucinationFailures: 5, - maxRepeatedGenerations: 10, - refinements: [], - refinePrompt: 'refinePrompt', - replacements: {}, - unrefinedResults: null, -}; - -describe('getGenerateOrEndEdge', () => { - beforeEach(() => jest.clearAllMocks()); - - it("returns 'end' when there are zero alerts", () => { - const state: GraphState = { - ...graphState, - anonymizedAlerts: [], // <-- zero alerts - }; - - const edge = getGenerateOrEndEdge(logger); - const result = edge(state); - - expect(result).toEqual('end'); - }); - - it("returns 'generate' when there are alerts", () => { - const edge = getGenerateOrEndEdge(logger); - const result = edge(graphState); - - expect(result).toEqual('generate'); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts deleted file mode 100644 index 5bfc4912298eb..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Logger } from '@kbn/core/server'; - -import { getGenerateOrEndDecision } from './helpers/get_generate_or_end_decision'; -import { getHasZeroAlerts } from '../helpers/get_has_zero_alerts'; -import type { GraphState } from '../../types'; - -export const getGenerateOrEndEdge = (logger?: Logger) => { - const edge = (state: GraphState): 'end' | 'generate' => { - logger?.debug(() => '---GENERATE OR END---'); - const { anonymizedAlerts } = state; - - const hasZeroAlerts = getHasZeroAlerts(anonymizedAlerts); - - const decision = getGenerateOrEndDecision(hasZeroAlerts); - - logger?.debug( - () => `generatOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( - { - anonymizedAlerts: anonymizedAlerts.length, - hasZeroAlerts, - }, - null, - 2 - )} -\n---GENERATE OR END: ${decision}---` - ); - return decision; - }; - - return edge; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts deleted file mode 100644 index 42c63b18459ed..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getGenerateOrRefineOrEndDecision } from '.'; - -describe('getGenerateOrRefineOrEndDecision', () => { - it("returns 'end' if getShouldEnd returns true", () => { - const result = getGenerateOrRefineOrEndDecision({ - hasUnrefinedResults: false, - hasZeroAlerts: true, - maxHallucinationFailuresReached: true, - maxRetriesReached: true, - }); - - expect(result).toEqual('end'); - }); - - it("returns 'refine' if hasUnrefinedResults is true and getShouldEnd returns false", () => { - const result = getGenerateOrRefineOrEndDecision({ - hasUnrefinedResults: true, - hasZeroAlerts: false, - maxHallucinationFailuresReached: false, - maxRetriesReached: false, - }); - - expect(result).toEqual('refine'); - }); - - it("returns 'generate' if hasUnrefinedResults is false and getShouldEnd returns false", () => { - const result = getGenerateOrRefineOrEndDecision({ - hasUnrefinedResults: false, - hasZeroAlerts: false, - maxHallucinationFailuresReached: false, - maxRetriesReached: false, - }); - - expect(result).toEqual('generate'); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts deleted file mode 100644 index b409f63f71a69..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getShouldEnd } from '../get_should_end'; - -export const getGenerateOrRefineOrEndDecision = ({ - hasUnrefinedResults, - hasZeroAlerts, - maxHallucinationFailuresReached, - maxRetriesReached, -}: { - hasUnrefinedResults: boolean; - hasZeroAlerts: boolean; - maxHallucinationFailuresReached: boolean; - maxRetriesReached: boolean; -}): 'end' | 'generate' | 'refine' => { - if (getShouldEnd({ hasZeroAlerts, maxHallucinationFailuresReached, maxRetriesReached })) { - return 'end'; - } else if (hasUnrefinedResults) { - return 'refine'; - } else { - return 'generate'; - } -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts deleted file mode 100644 index 82480a6ad6889..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getShouldEnd } from '.'; - -describe('getShouldEnd', () => { - it('returns true if hasZeroAlerts is true', () => { - const result = getShouldEnd({ - hasZeroAlerts: true, // <-- true - maxHallucinationFailuresReached: false, - maxRetriesReached: false, - }); - - expect(result).toBe(true); - }); - - it('returns true if maxHallucinationFailuresReached is true', () => { - const result = getShouldEnd({ - hasZeroAlerts: false, - maxHallucinationFailuresReached: true, // <-- true - maxRetriesReached: false, - }); - - expect(result).toBe(true); - }); - - it('returns true if maxRetriesReached is true', () => { - const result = getShouldEnd({ - hasZeroAlerts: false, - maxHallucinationFailuresReached: false, - maxRetriesReached: true, // <-- true - }); - - expect(result).toBe(true); - }); - - it('returns false if all conditions are false', () => { - const result = getShouldEnd({ - hasZeroAlerts: false, - maxHallucinationFailuresReached: false, - maxRetriesReached: false, - }); - - expect(result).toBe(false); - }); - - it('returns true if all conditions are true', () => { - const result = getShouldEnd({ - hasZeroAlerts: true, - maxHallucinationFailuresReached: true, - maxRetriesReached: true, - }); - - expect(result).toBe(true); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts deleted file mode 100644 index 9724ba25886fa..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getShouldEnd = ({ - hasZeroAlerts, - maxHallucinationFailuresReached, - maxRetriesReached, -}: { - hasZeroAlerts: boolean; - maxHallucinationFailuresReached: boolean; - maxRetriesReached: boolean; -}): boolean => hasZeroAlerts || maxRetriesReached || maxHallucinationFailuresReached; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts deleted file mode 100644 index 585a1bc2dcac3..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { loggerMock } from '@kbn/logging-mocks'; - -import { getGenerateOrRefineOrEndEdge } from '.'; -import type { GraphState } from '../../types'; - -const logger = loggerMock.create(); - -const graphState: GraphState = { - attackDiscoveries: null, - attackDiscoveryPrompt: 'prompt', - anonymizedAlerts: [ - { - metadata: {}, - pageContent: - '@timestamp,2024-10-10T21:01:24.148Z\n' + - '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + - 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + - 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', - }, - { - metadata: {}, - pageContent: - '@timestamp,2024-10-10T21:01:24.148Z\n' + - '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + - 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + - 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', - }, - ], - combinedGenerations: '', - combinedRefinements: '', - errors: [], - generationAttempts: 0, - generations: [], - hallucinationFailures: 0, - maxGenerationAttempts: 10, - maxHallucinationFailures: 5, - maxRepeatedGenerations: 3, - refinements: [], - refinePrompt: 'refinePrompt', - replacements: {}, - unrefinedResults: null, -}; - -describe('getGenerateOrRefineOrEndEdge', () => { - beforeEach(() => jest.clearAllMocks()); - - it('returns "end" when there are zero alerts', () => { - const withZeroAlerts: GraphState = { - ...graphState, - anonymizedAlerts: [], // <-- zero alerts - }; - - const edge = getGenerateOrRefineOrEndEdge(logger); - const result = edge(withZeroAlerts); - - expect(result).toEqual('end'); - }); - - it('returns "end" when max hallucination failures are reached', () => { - const withMaxHallucinationFailures: GraphState = { - ...graphState, - hallucinationFailures: 5, - }; - - const edge = getGenerateOrRefineOrEndEdge(logger); - const result = edge(withMaxHallucinationFailures); - - expect(result).toEqual('end'); - }); - - it('returns "end" when max retries are reached', () => { - const withMaxRetries: GraphState = { - ...graphState, - generationAttempts: 10, - }; - - const edge = getGenerateOrRefineOrEndEdge(logger); - const result = edge(withMaxRetries); - - expect(result).toEqual('end'); - }); - - it('returns refine when there are unrefined results', () => { - const withUnrefinedResults: GraphState = { - ...graphState, - unrefinedResults: [ - { - alertIds: [], - id: 'test-id', - detailsMarkdown: 'test-details', - entitySummaryMarkdown: 'test-summary', - summaryMarkdown: 'test-summary', - title: 'test-title', - timestamp: '2024-10-10T21:01:24.148Z', - }, - ], - }; - - const edge = getGenerateOrRefineOrEndEdge(logger); - const result = edge(withUnrefinedResults); - - expect(result).toEqual('refine'); - }); - - it('return generate when there are no unrefined results', () => { - const edge = getGenerateOrRefineOrEndEdge(logger); - const result = edge(graphState); - - expect(result).toEqual('generate'); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts deleted file mode 100644 index 3368a04ec9204..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Logger } from '@kbn/core/server'; - -import { getGenerateOrRefineOrEndDecision } from './helpers/get_generate_or_refine_or_end_decision'; -import { getHasResults } from '../helpers/get_has_results'; -import { getHasZeroAlerts } from '../helpers/get_has_zero_alerts'; -import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; -import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; -import type { GraphState } from '../../types'; - -export const getGenerateOrRefineOrEndEdge = (logger?: Logger) => { - const edge = (state: GraphState): 'end' | 'generate' | 'refine' => { - logger?.debug(() => '---GENERATE OR REFINE OR END---'); - const { - anonymizedAlerts, - generationAttempts, - hallucinationFailures, - maxGenerationAttempts, - maxHallucinationFailures, - unrefinedResults, - } = state; - - const hasZeroAlerts = getHasZeroAlerts(anonymizedAlerts); - const hasUnrefinedResults = getHasResults(unrefinedResults); - const maxRetriesReached = getMaxRetriesReached({ generationAttempts, maxGenerationAttempts }); - const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ - hallucinationFailures, - maxHallucinationFailures, - }); - - const decision = getGenerateOrRefineOrEndDecision({ - hasUnrefinedResults, - hasZeroAlerts, - maxHallucinationFailuresReached, - maxRetriesReached, - }); - - logger?.debug( - () => - `generatOrRefineOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( - { - anonymizedAlerts: anonymizedAlerts.length, - generationAttempts, - hallucinationFailures, - hasUnrefinedResults, - hasZeroAlerts, - maxHallucinationFailuresReached, - maxRetriesReached, - unrefinedResults: unrefinedResults?.length ?? 0, - }, - null, - 2 - )} - \n---GENERATE OR REFINE OR END: ${decision}---` - ); - return decision; - }; - - return edge; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts deleted file mode 100644 index 413f01b74dece..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscovery } from '@kbn/elastic-assistant-common'; - -export const getHasResults = (attackDiscoveries: AttackDiscovery[] | null): boolean => - attackDiscoveries !== null; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts deleted file mode 100644 index d768b363f101e..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Document } from '@langchain/core/documents'; -import { isEmpty } from 'lodash/fp'; - -export const getHasZeroAlerts = (anonymizedAlerts: Document[]): boolean => - isEmpty(anonymizedAlerts); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts deleted file mode 100644 index 7168aa08aeef2..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getShouldEnd } from '../get_should_end'; - -export const getRefineOrEndDecision = ({ - hasFinalResults, - maxHallucinationFailuresReached, - maxRetriesReached, -}: { - hasFinalResults: boolean; - maxHallucinationFailuresReached: boolean; - maxRetriesReached: boolean; -}): 'refine' | 'end' => - getShouldEnd({ - hasFinalResults, - maxHallucinationFailuresReached, - maxRetriesReached, - }) - ? 'end' - : 'refine'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts deleted file mode 100644 index 697f93dd3a02f..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getShouldEnd = ({ - hasFinalResults, - maxHallucinationFailuresReached, - maxRetriesReached, -}: { - hasFinalResults: boolean; - maxHallucinationFailuresReached: boolean; - maxRetriesReached: boolean; -}): boolean => hasFinalResults || maxRetriesReached || maxHallucinationFailuresReached; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts deleted file mode 100644 index 85140dceafdcb..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Logger } from '@kbn/core/server'; - -import { getRefineOrEndDecision } from './helpers/get_refine_or_end_decision'; -import { getHasResults } from '../helpers/get_has_results'; -import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; -import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; -import type { GraphState } from '../../types'; - -export const getRefineOrEndEdge = (logger?: Logger) => { - const edge = (state: GraphState): 'end' | 'refine' => { - logger?.debug(() => '---REFINE OR END---'); - const { - attackDiscoveries, - generationAttempts, - hallucinationFailures, - maxGenerationAttempts, - maxHallucinationFailures, - } = state; - - const hasFinalResults = getHasResults(attackDiscoveries); - const maxRetriesReached = getMaxRetriesReached({ generationAttempts, maxGenerationAttempts }); - const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ - hallucinationFailures, - maxHallucinationFailures, - }); - - const decision = getRefineOrEndDecision({ - hasFinalResults, - maxHallucinationFailuresReached, - maxRetriesReached, - }); - - logger?.debug( - () => - `refineOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( - { - attackDiscoveries: attackDiscoveries?.length ?? 0, - generationAttempts, - hallucinationFailures, - hasFinalResults, - maxHallucinationFailuresReached, - maxRetriesReached, - }, - null, - 2 - )} - \n---REFINE OR END: ${decision}---` - ); - - return decision; - }; - - return edge; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts deleted file mode 100644 index 050ca17484185..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Document } from '@langchain/core/documents'; - -export const getRetrieveOrGenerate = ( - anonymizedAlerts: Document[] -): 'retrieve_anonymized_alerts' | 'generate' => - anonymizedAlerts.length === 0 ? 'retrieve_anonymized_alerts' : 'generate'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts deleted file mode 100644 index ad0512497d07d..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Logger } from '@kbn/core/server'; - -import { getRetrieveOrGenerate } from './get_retrieve_or_generate'; -import type { GraphState } from '../../types'; - -export const getRetrieveAnonymizedAlertsOrGenerateEdge = (logger?: Logger) => { - const edge = (state: GraphState): 'retrieve_anonymized_alerts' | 'generate' => { - logger?.debug(() => '---RETRIEVE ANONYMIZED ALERTS OR GENERATE---'); - const { anonymizedAlerts } = state; - - const decision = getRetrieveOrGenerate(anonymizedAlerts); - - logger?.debug( - () => - `retrieveAnonymizedAlertsOrGenerateEdge evaluated the following (derived) state:\n${JSON.stringify( - { - anonymizedAlerts: anonymizedAlerts.length, - }, - null, - 2 - )} - \n---RETRIEVE ANONYMIZED ALERTS OR GENERATE: ${decision}---` - ); - - return decision; - }; - - return edge; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts deleted file mode 100644 index 07985381afa73..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getMaxHallucinationFailuresReached = ({ - hallucinationFailures, - maxHallucinationFailures, -}: { - hallucinationFailures: number; - maxHallucinationFailures: number; -}): boolean => hallucinationFailures >= maxHallucinationFailures; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts deleted file mode 100644 index c1e36917b45cf..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getMaxRetriesReached = ({ - generationAttempts, - maxGenerationAttempts, -}: { - generationAttempts: number; - maxGenerationAttempts: number; -}): boolean => generationAttempts >= maxGenerationAttempts; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts deleted file mode 100644 index b2c90636ef523..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { Replacements } from '@kbn/elastic-assistant-common'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import type { CompiledStateGraph } from '@langchain/langgraph'; -import { END, START, StateGraph } from '@langchain/langgraph'; - -import { NodeType } from './constants'; -import { getGenerateOrEndEdge } from './edges/generate_or_end'; -import { getGenerateOrRefineOrEndEdge } from './edges/generate_or_refine_or_end'; -import { getRefineOrEndEdge } from './edges/refine_or_end'; -import { getRetrieveAnonymizedAlertsOrGenerateEdge } from './edges/retrieve_anonymized_alerts_or_generate'; -import { getDefaultGraphState } from './state'; -import { getGenerateNode } from './nodes/generate'; -import { getRefineNode } from './nodes/refine'; -import { getRetrieveAnonymizedAlertsNode } from './nodes/retriever'; -import type { GraphState } from './types'; - -export interface GetDefaultAttackDiscoveryGraphParams { - alertsIndexPattern?: string; - anonymizationFields: AnonymizationFieldResponse[]; - esClient: ElasticsearchClient; - llm: ActionsClientLlm; - logger?: Logger; - onNewReplacements?: (replacements: Replacements) => void; - replacements?: Replacements; - size: number; -} - -export type DefaultAttackDiscoveryGraph = ReturnType<typeof getDefaultAttackDiscoveryGraph>; - -/** - * This function returns a compiled state graph that represents the default - * Attack discovery graph. - * - * Refer to the following diagram for this graph: - * x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png - */ -export const getDefaultAttackDiscoveryGraph = ({ - alertsIndexPattern, - anonymizationFields, - esClient, - llm, - logger, - onNewReplacements, - replacements, - size, -}: GetDefaultAttackDiscoveryGraphParams): CompiledStateGraph< - GraphState, - Partial<GraphState>, - 'generate' | 'refine' | 'retrieve_anonymized_alerts' | '__start__' -> => { - try { - const graphState = getDefaultGraphState(); - - // get nodes: - const retrieveAnonymizedAlertsNode = getRetrieveAnonymizedAlertsNode({ - alertsIndexPattern, - anonymizationFields, - esClient, - logger, - onNewReplacements, - replacements, - size, - }); - - const generateNode = getGenerateNode({ - llm, - logger, - }); - - const refineNode = getRefineNode({ - llm, - logger, - }); - - // get edges: - const generateOrEndEdge = getGenerateOrEndEdge(logger); - - const generatOrRefineOrEndEdge = getGenerateOrRefineOrEndEdge(logger); - - const refineOrEndEdge = getRefineOrEndEdge(logger); - - const retrieveAnonymizedAlertsOrGenerateEdge = - getRetrieveAnonymizedAlertsOrGenerateEdge(logger); - - // create the graph: - const graph = new StateGraph<GraphState>({ channels: graphState }) - .addNode(NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, retrieveAnonymizedAlertsNode) - .addNode(NodeType.GENERATE_NODE, generateNode) - .addNode(NodeType.REFINE_NODE, refineNode) - .addConditionalEdges(START, retrieveAnonymizedAlertsOrGenerateEdge, { - generate: NodeType.GENERATE_NODE, - retrieve_anonymized_alerts: NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, - }) - .addConditionalEdges(NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, generateOrEndEdge, { - end: END, - generate: NodeType.GENERATE_NODE, - }) - .addConditionalEdges(NodeType.GENERATE_NODE, generatOrRefineOrEndEdge, { - end: END, - generate: NodeType.GENERATE_NODE, - refine: NodeType.REFINE_NODE, - }) - .addConditionalEdges(NodeType.REFINE_NODE, refineOrEndEdge, { - end: END, - refine: NodeType.REFINE_NODE, - }); - - // compile the graph: - return graph.compile(); - } catch (e) { - throw new Error(`Unable to compile AttackDiscoveryGraph\n${e}`); - } -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts deleted file mode 100644 index ed5549acc586a..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const mockEmptyOpenAndAcknowledgedAlertsQueryResults = { - took: 0, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 0, - relation: 'eq', - }, - max_score: null, - hits: [], - }, -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts deleted file mode 100644 index 3f22f787f54f8..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts +++ /dev/null @@ -1,1396 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const mockOpenAndAcknowledgedAlertsQueryResults = { - took: 13, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 31, - relation: 'eq', - }, - max_score: null, - hits: [ - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['/Users/james/unix1'], - 'process.hash.md5': ['85caafe3d324e3287b85348fa2fae492'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': [ - '/Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!!', - ], - 'process.parent.name': ['unix1'], - 'user.name': ['james'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231', - ], - 'process.code_signature.signing_id': ['nans-55554944e5f232edcf023cf68e8e5dac81584f78'], - 'process.pid': [1227], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': [ - 'code failed to satisfy specified code requirement(s)', - ], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': [''], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.72442], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': ['/Users/james/unix1'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.parent.code_signature.subject_name': [''], - 'process.parent.executable': ['/Users/james/unix1'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['unix1'], - 'process.args': [ - '/Users/james/unix1', - '/Users/james/library/Keychains/login.keychain-db', - 'TempTemp1234!!', - ], - 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [3], - 'process.name': ['unix1'], - 'process.parent.args': [ - '/Users/james/unix1', - '/Users/james/library/Keychains/login.keychain-db', - 'TempTemp1234!!', - ], - '@timestamp': ['2024-05-07T12:48:45.032Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': [ - '/Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!!', - ], - 'host.risk.calculated_level': ['High'], - _id: ['b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560'], - 'process.hash.sha1': ['4ca549355736e4af6434efc4ec9a044ceb2ae3c3'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:28:39.368Z'], - }, - sort: [99, 1715086125032], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['/Users/james/unix1'], - 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], - 'event.category': ['malware', 'intrusion_detection', 'file'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.parent.name': ['My Go Application.app'], - 'user.name': ['james'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', - ], - 'process.code_signature.signing_id': ['a.out'], - 'process.pid': [1220], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': [ - 'code failed to satisfy specified code requirement(s)', - ], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': [''], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.72442], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.parent.code_signature.subject_name': [''], - 'process.parent.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['unix1'], - 'process.args': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['My Go Application.app'], - 'process.parent.args': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - '@timestamp': ['2024-05-07T12:48:45.030Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'host.risk.calculated_level': ['High'], - _id: ['0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367'], - 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:28:38.061Z'], - }, - sort: [99, 1715086125030], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['/Users/james/unix1'], - 'process.hash.md5': ['85caafe3d324e3287b85348fa2fae492'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.parent.name': ['My Go Application.app'], - 'user.name': ['james'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231', - ], - 'process.code_signature.signing_id': ['nans-55554944e5f232edcf023cf68e8e5dac81584f78'], - 'process.pid': [1220], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': [ - 'code failed to satisfy specified code requirement(s)', - ], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': [''], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.72442], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': ['/Users/james/unix1'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.parent.code_signature.subject_name': [''], - 'process.parent.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['unix1'], - 'process.args': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['unix1'], - 'process.parent.args': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - '@timestamp': ['2024-05-07T12:48:45.029Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'host.risk.calculated_level': ['High'], - _id: ['600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a'], - 'process.hash.sha1': ['4ca549355736e4af6434efc4ec9a044ceb2ae3c3'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:28:37.881Z'], - }, - sort: [99, 1715086125029], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['/Users/james/unix1'], - 'process.hash.md5': ['3f19892ab44eb9bc7bc03f438944301e'], - 'event.category': ['malware', 'intrusion_detection', 'file'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.parent.name': ['My Go Application.app'], - 'user.name': ['james'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - 'f80234ff6fed2c62d23f37443f2412fbe806711b6add2ac126e03e282082c8f5', - ], - 'process.code_signature.signing_id': ['com.apple.chmod'], - 'process.pid': [1219], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': [ - 'code failed to satisfy specified code requirement(s)', - ], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Software Signing'], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.72442], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': ['/bin/chmod'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'process.parent.code_signature.subject_name': [''], - 'process.parent.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['unix1'], - 'process.args': ['chmod', '777', '/Users/james/unix1'], - 'process.code_signature.status': ['No error.'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['chmod'], - 'process.parent.args': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - '@timestamp': ['2024-05-07T12:48:45.028Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': ['chmod 777 /Users/james/unix1'], - 'host.risk.calculated_level': ['High'], - _id: ['e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c'], - 'process.hash.sha1': ['217490d4f51717aa3b301abec96be08602370d2d'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:28:37.869Z'], - }, - sort: [99, 1715086125028], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['643dddff1a57cbf70594854b44eb1a1d'], - 'event.category': ['malware', 'intrusion_detection'], - 'host.risk.calculated_score_norm': [73.02488], - 'rule.reference': [ - 'https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/prompt.py', - 'https://ss64.com/osx/osascript.html', - ], - 'process.parent.name': ['My Go Application.app'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - 'bab17feba710b469e5d96820f0cb7ed511d983e5817f374ec3cb46462ac5b794', - ], - 'process.pid': [1206], - 'process.code_signature.exists': [true], - 'process.code_signature.subject_name': ['Software Signing'], - 'host.os.version': ['13.4'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.72442], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': [ - 'Malicious Behavior Detection Alert: Potential Credentials Phishing via OSASCRIPT', - ], - 'host.name': ['SRVMAC08'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'group.name': ['staff'], - 'kibana.alert.workflow_status': ['open'], - 'rule.name': ['Potential Credentials Phishing via OSASCRIPT'], - 'threat.tactic.id': ['TA0006'], - 'threat.tactic.name': ['Credential Access'], - 'threat.technique.id': ['T1056'], - 'process.parent.args_count': [0], - 'threat.technique.subtechnique.reference': [ - 'https://attack.mitre.org/techniques/T1056/002/', - ], - 'process.name': ['osascript'], - 'threat.technique.subtechnique.name': ['GUI Input Capture'], - 'process.parent.code_signature.trusted': [false], - _id: ['2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f'], - 'threat.technique.name': ['Input Capture'], - 'group.id': ['20'], - 'threat.tactic.reference': ['https://attack.mitre.org/tactics/TA0006/'], - 'user.name': ['james'], - 'threat.framework': ['MITRE ATT&CK'], - 'process.code_signature.signing_id': ['com.apple.osascript'], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': [ - 'code failed to satisfy specified code requirement(s)', - ], - 'event.module': ['endpoint'], - 'process.executable': ['/usr/bin/osascript'], - 'process.parent.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.args': [ - 'osascript', - '-e', - 'display dialog "MacOS wants to access System Preferences\n\t\t\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬', - ], - 'process.code_signature.status': ['No error.'], - message: [ - 'Malicious Behavior Detection Alert: Potential Credentials Phishing via OSASCRIPT', - ], - '@timestamp': ['2024-05-07T12:48:45.027Z'], - 'threat.technique.subtechnique.id': ['T1056.002'], - 'threat.technique.reference': ['https://attack.mitre.org/techniques/T1056/'], - 'process.command_line': [ - 'osascript -e display dialog "MacOS wants to access System Preferences\n\t\t\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬', - ], - 'host.risk.calculated_level': ['High'], - 'process.hash.sha1': ['0568baae15c752208ae56d8f9c737976d6de2e3a'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:28:09.909Z'], - }, - sort: [99, 1715086125027], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '2a9f7602de8656d30dda0ddcf79e78037ac2929780e13d5b2047b3bedc40bb69', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': ['/sbin/launchd'], - 'process.parent.name': ['launchd'], - 'user.name': ['root'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', - ], - 'process.code_signature.signing_id': ['a.out'], - 'process.pid': [1200], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['No error.'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': [''], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.491455], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.parent.code_signature.subject_name': ['Software Signing'], - 'process.parent.executable': ['/sbin/launchd'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['My Go Application.app'], - 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], - 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['My Go Application.app'], - 'process.parent.args': ['/sbin/launchd'], - '@timestamp': ['2024-05-07T12:48:45.023Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': [ - 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', - ], - 'host.risk.calculated_level': ['High'], - _id: ['2a9f7602de8656d30dda0ddcf79e78037ac2929780e13d5b2047b3bedc40bb69'], - 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:28:06.888Z'], - }, - sort: [99, 1715086125023], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '4615c3a90e8057ae5cc9b358bbbf4298e346277a2f068dda052b0b43ef6d5bbd', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/3C4D44B9-4838-4613-BACC-BD00A9CE4025/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': ['/sbin/launchd'], - 'process.parent.name': ['launchd'], - 'user.name': ['root'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', - ], - 'process.code_signature.signing_id': ['a.out'], - 'process.pid': [1169], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['No error.'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': [''], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.491455], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/3C4D44B9-4838-4613-BACC-BD00A9CE4025/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.parent.code_signature.subject_name': ['Software Signing'], - 'process.parent.executable': ['/sbin/launchd'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['My Go Application.app'], - 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], - 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['My Go Application.app'], - 'process.parent.args': ['/sbin/launchd'], - '@timestamp': ['2024-05-07T12:48:45.022Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': [ - 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', - ], - 'host.risk.calculated_level': ['High'], - _id: ['4615c3a90e8057ae5cc9b358bbbf4298e346277a2f068dda052b0b43ef6d5bbd'], - 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:27:47.362Z'], - }, - sort: [99, 1715086125022], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '449322a72d3f19efbdf983935a1bdd21ebd6b9c761ce31e8b252003017d7e5db', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/37D933EC-334D-410A-A741-0F730D6AE3FD/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': ['/sbin/launchd'], - 'process.parent.name': ['launchd'], - 'user.name': ['root'], - 'user.risk.calculated_level': ['Moderate'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', - ], - 'process.code_signature.signing_id': ['a.out'], - 'process.pid': [1123], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['No error.'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': [''], - 'host.os.version': ['13.4'], - 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [66.491455], - 'host.os.name': ['macOS'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVMAC08'], - 'process.executable': [ - '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/37D933EC-334D-410A-A741-0F730D6AE3FD/d/Setup.app/Contents/MacOS/My Go Application.app', - ], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.parent.code_signature.subject_name': ['Software Signing'], - 'process.parent.executable': ['/sbin/launchd'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['My Go Application.app'], - 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], - 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['My Go Application.app'], - 'process.parent.args': ['/sbin/launchd'], - '@timestamp': ['2024-05-07T12:48:45.020Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': [ - 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', - ], - 'host.risk.calculated_level': ['High'], - _id: ['449322a72d3f19efbdf983935a1bdd21ebd6b9c761ce31e8b252003017d7e5db'], - 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-06-19T00:25:24.716Z'], - }, - sort: [99, 1715086125020], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'f465ca9fbfc8bc3b1871e965c9e111cac76ff3f4076fed6bc9da88d49fb43014', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], - 'event.category': ['malware', 'intrusion_detection'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'process.parent.name': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', - ], - 'process.pid': [8708], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['errorExpired'], - 'process.pe.original_file_name': ['MsMpEng.exe'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Corporation'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'process.parent.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'kibana.alert.workflow_status': ['open'], - 'process.args': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.status': ['trusted'], - message: ['Memory Threat Detection Alert: Shellcode Injection'], - 'process.parent.args_count': [1], - 'process.name': ['MsMpEng.exe'], - 'process.parent.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - '@timestamp': ['2024-05-07T12:48:45.017Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], - 'host.risk.calculated_level': ['High'], - _id: ['f465ca9fbfc8bc3b1871e965c9e111cac76ff3f4076fed6bc9da88d49fb43014'], - 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:22.051Z'], - }, - sort: [99, 1715086125017], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'aa283e6a13be77b533eceffb09e48254c8f91feeccc39f7eed80fd3881d053f4', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['C:\\Windows\\mpsvc.dll'], - 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], - 'event.category': ['malware', 'intrusion_detection', 'library'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'process.parent.name': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', - ], - 'process.pid': [8708], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['errorExpired'], - 'process.pe.original_file_name': ['MsMpEng.exe'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Corporation'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'file.hash.sha256': ['8dd620d9aeb35960bb766458c8890ede987c33d239cf730f93fe49d90ae759dd'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\MsMpEng.exe'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'process.parent.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['mpsvc.dll'], - 'process.args': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.status': ['trusted'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['MsMpEng.exe'], - 'process.parent.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - '@timestamp': ['2024-05-07T12:48:45.008Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], - 'host.risk.calculated_level': ['High'], - _id: ['aa283e6a13be77b533eceffb09e48254c8f91feeccc39f7eed80fd3881d053f4'], - 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:18.093Z'], - }, - sort: [99, 1715086125008], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'dd9e4ea23961ccfdb7a9c760ee6bedd19a013beac3b0d38227e7ae77ba4ce515', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['C:\\Windows\\mpsvc.dll'], - 'process.hash.md5': ['561cffbaba71a6e8cc1cdceda990ead4'], - 'event.category': ['malware', 'intrusion_detection', 'file'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': ['C:\\Windows\\Explorer.EXE'], - 'process.parent.name': ['explorer.exe'], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', - ], - 'process.pid': [1008], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['trusted'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'file.hash.sha256': ['8dd620d9aeb35960bb766458c8890ede987c33d239cf730f93fe49d90ae759dd'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['Microsoft Windows'], - 'process.parent.executable': ['C:\\Windows\\explorer.exe'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['mpsvc.dll'], - 'process.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'process.code_signature.status': ['errorExpired'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], - 'process.parent.args': ['C:\\Windows\\Explorer.EXE'], - '@timestamp': ['2024-05-07T12:48:45.007Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'host.risk.calculated_level': ['High'], - _id: ['dd9e4ea23961ccfdb7a9c760ee6bedd19a013beac3b0d38227e7ae77ba4ce515'], - 'process.hash.sha1': ['5162f14d75e96edb914d1756349d6e11583db0b0'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:17.887Z'], - }, - sort: [99, 1715086125007], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'f30d55e503b1d848b34ee57741b203d8052360dd873ea34802f3fa7a9ef34d0a', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'process.hash.md5': ['561cffbaba71a6e8cc1cdceda990ead4'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': ['C:\\Windows\\Explorer.EXE'], - 'process.parent.name': ['explorer.exe'], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', - ], - 'process.pid': [1008], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['trusted'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [false], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['Microsoft Windows'], - 'process.parent.executable': ['C:\\Windows\\explorer.exe'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], - 'process.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'process.code_signature.status': ['errorExpired'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], - 'process.parent.args': ['C:\\Windows\\Explorer.EXE'], - '@timestamp': ['2024-05-07T12:48:45.006Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'host.risk.calculated_level': ['High'], - _id: ['f30d55e503b1d848b34ee57741b203d8052360dd873ea34802f3fa7a9ef34d0a'], - 'process.hash.sha1': ['5162f14d75e96edb914d1756349d6e11583db0b0'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:17.544Z'], - }, - sort: [99, 1715086125006], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '6f8cd5e8021dbb64598f2b7ec56bee21fd00d1e62d4e08905f86bf234873ee66', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'process.hash.md5': ['f070b5cf25febb9a88a168efd87c6112'], - 'event.category': ['malware', 'intrusion_detection', 'file'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [''], - 'process.parent.name': ['userinit.exe'], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '567be4d1e15f4ff96d92e7d28e191076f5813f50be96bf4c3916e4ecf53f66cd', - ], - 'process.pid': [6228], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['trusted'], - 'process.pe.original_file_name': ['EXPLORER.EXE'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Windows'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\explorer.exe'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['Microsoft Windows'], - 'process.parent.executable': ['C:\\Windows\\System32\\userinit.exe'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], - 'process.args': ['C:\\Windows\\Explorer.EXE'], - 'process.code_signature.status': ['trusted'], - message: ['Malware Detection Alert'], - 'process.name': ['explorer.exe'], - '@timestamp': ['2024-05-07T12:48:45.004Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': ['C:\\Windows\\Explorer.EXE'], - 'host.risk.calculated_level': ['High'], - _id: ['6f8cd5e8021dbb64598f2b7ec56bee21fd00d1e62d4e08905f86bf234873ee66'], - 'process.hash.sha1': ['94518c310478e494082418ed295466f5aea26eea'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:37:18.152Z'], - }, - sort: [99, 1715086125004], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'ce110da958fe0cf0c07599a21c68d90a64c93b7607aa27970a614c7f49598316', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', - ], - 'process.hash.md5': ['f070b5cf25febb9a88a168efd87c6112'], - 'event.category': ['malware', 'intrusion_detection', 'file'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [''], - 'process.parent.name': ['userinit.exe'], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '567be4d1e15f4ff96d92e7d28e191076f5813f50be96bf4c3916e4ecf53f66cd', - ], - 'process.pid': [6228], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['trusted'], - 'process.pe.original_file_name': ['EXPLORER.EXE'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Windows'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\explorer.exe'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['Microsoft Windows'], - 'process.parent.executable': ['C:\\Windows\\System32\\userinit.exe'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], - 'process.args': ['C:\\Windows\\Explorer.EXE'], - 'process.code_signature.status': ['trusted'], - message: ['Malware Detection Alert'], - 'process.name': ['explorer.exe'], - '@timestamp': ['2024-05-07T12:48:45.001Z'], - 'process.parent.code_signature.trusted': [true], - 'process.command_line': ['C:\\Windows\\Explorer.EXE'], - 'host.risk.calculated_level': ['High'], - _id: ['ce110da958fe0cf0c07599a21c68d90a64c93b7607aa27970a614c7f49598316'], - 'process.hash.sha1': ['94518c310478e494082418ed295466f5aea26eea'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:36:43.813Z'], - }, - sort: [99, 1715086125001], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '0866787b0027b4d908767ac16e35a1da00970c83632ba85be65f2ad371132b4f', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], - 'event.category': ['malware', 'intrusion_detection', 'process', 'file'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'process.parent.name': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', - ], - 'process.pid': [8708], - 'process.code_signature.exists': [true], - 'process.code_signature.subject_name': ['Microsoft Corporation'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Ransomware Detection Alert'], - 'host.name': ['SRVWIN02'], - 'Ransomware.files.data': [ - '2D002D002D003D003D003D0020005700', - '2D002D002D003D003D003D0020005700', - '2D002D002D003D003D003D0020005700', - ], - 'process.code_signature.trusted': [true], - 'Ransomware.files.metrics': ['CANARY_ACTIVITY'], - 'kibana.alert.workflow_status': ['open'], - 'process.parent.args_count': [1], - 'process.name': ['MsMpEng.exe'], - 'Ransomware.files.score': [0, 0, 0], - 'process.parent.code_signature.trusted': [false], - _id: ['0866787b0027b4d908767ac16e35a1da00970c83632ba85be65f2ad371132b4f'], - 'Ransomware.version': ['1.6.0'], - 'user.name': ['Administrator'], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['errorExpired'], - 'Ransomware.files.operation': ['creation', 'creation', 'creation'], - 'process.pe.original_file_name': ['MsMpEng.exe'], - 'event.module': ['endpoint'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\MsMpEng.exe'], - 'process.Ext.token.integrity_level_name': ['high'], - 'Ransomware.files.path': [ - 'c:\\hd3vuk19y-readme.txt', - 'c:\\$winreagent\\hd3vuk19y-readme.txt', - 'c:\\aaantiransomelastic-do-not-touch-dab6d40c-a6a1-442c-adc4-9d57a47e58d7\\hd3vuk19y-readme.txt', - ], - 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'process.parent.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'Ransomware.files.entropy': [3.629971457026797, 3.629971457026797, 3.629971457026797], - 'Ransomware.feature': ['canary'], - 'Ransomware.files.extension': ['txt', 'txt', 'txt'], - 'process.args': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.status': ['trusted'], - message: ['Ransomware Detection Alert'], - 'process.parent.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - '@timestamp': ['2024-05-07T12:48:45.000Z'], - 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], - 'host.risk.calculated_level': ['High'], - 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:22.964Z'], - }, - sort: [99, 1715086125000], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'b0fdf96721e361e1137d49a67e26d92f96b146392d7f44322bddc3d660abaef1', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], - 'event.category': ['malware', 'intrusion_detection'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'process.parent.name': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', - ], - 'process.pid': [8708], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['errorExpired'], - 'process.pe.original_file_name': ['MsMpEng.exe'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Corporation'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'process.parent.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'kibana.alert.workflow_status': ['open'], - 'process.args': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.status': ['trusted'], - message: ['Memory Threat Detection Alert: Shellcode Injection'], - 'process.parent.args_count': [1], - 'process.name': ['MsMpEng.exe'], - 'process.parent.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - '@timestamp': ['2024-05-07T12:48:44.996Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], - 'host.risk.calculated_level': ['High'], - _id: ['b0fdf96721e361e1137d49a67e26d92f96b146392d7f44322bddc3d660abaef1'], - 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:22.174Z'], - }, - sort: [99, 1715086124996], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '7b4f49f21cf141e67856d3207fb4ea069c8035b41f0ea501970694cf8bd43cbe', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], - 'event.category': ['malware', 'intrusion_detection'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'process.parent.name': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', - ], - 'process.pid': [8708], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['errorExpired'], - 'process.pe.original_file_name': ['MsMpEng.exe'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Corporation'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], - 'host.name': ['SRVWIN02'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'process.parent.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'kibana.alert.workflow_status': ['open'], - 'process.args': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.status': ['trusted'], - message: ['Memory Threat Detection Alert: Shellcode Injection'], - 'process.parent.args_count': [1], - 'process.name': ['MsMpEng.exe'], - 'process.parent.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - '@timestamp': ['2024-05-07T12:48:44.986Z'], - 'process.parent.code_signature.trusted': [false], - 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], - 'host.risk.calculated_level': ['High'], - _id: ['7b4f49f21cf141e67856d3207fb4ea069c8035b41f0ea501970694cf8bd43cbe'], - 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:22.066Z'], - }, - sort: [99, 1715086124986], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'ea81d79104cbd442236b5bcdb7a3331de897aa4ce1523e622068038d048d0a9e', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], - 'event.category': ['malware', 'intrusion_detection', 'process'], - 'host.risk.calculated_score_norm': [75.62723], - 'process.parent.command_line': [ - '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', - ], - 'process.parent.name': [ - 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', - ], - 'process.Ext.memory_region.malware_signature.primary.matches': [ - 'WVmF9nQli1UIg2YEAIk+iwoLSgQ=', - 'dQxy0zPAQF9eW4vlXcMzwOv1VYvsgw==', - 'DIsEsIN4BAV1HP9wCP9wDP91DP8=', - '+4tF/FCLCP9RCF6Lx19bi+Vdw1U=', - 'vAAAADPSi030i/GLRfAPpMEBwe4f', - 'VIvO99GLwiNN3PfQM030I8czReiJ', - 'DIlGDIXAdSozwOtsi0YIhcB0Yms=', - ], - 'process.pid': [8708], - 'process.code_signature.exists': [true], - 'process.code_signature.subject_name': ['Microsoft Corporation'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': [ - 'Memory Threat Detection Alert: Windows.Ransomware.Sodinokibi', - ], - 'host.name': ['SRVWIN02'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'kibana.alert.workflow_status': ['open'], - 'rule.name': ['Windows.Ransomware.Sodinokibi'], - 'process.parent.args_count': [1], - 'process.Ext.memory_region.bytes_compressed_present': [false], - 'process.name': ['MsMpEng.exe'], - 'process.parent.code_signature.trusted': [false], - _id: ['ea81d79104cbd442236b5bcdb7a3331de897aa4ce1523e622068038d048d0a9e'], - 'user.name': ['Administrator'], - 'process.parent.code_signature.exists': [true], - 'process.parent.code_signature.status': ['errorExpired'], - 'process.pe.original_file_name': ['MsMpEng.exe'], - 'event.module': ['endpoint'], - 'process.Ext.memory_region.malware_signature.all_names': [ - 'Windows.Ransomware.Sodinokibi', - ], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\MsMpEng.exe'], - 'process.Ext.memory_region.malware_signature.primary.signature.name': [ - 'Windows.Ransomware.Sodinokibi', - ], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], - 'process.parent.executable': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - 'process.args': ['C:\\Windows\\MsMpEng.exe'], - 'process.code_signature.status': ['trusted'], - message: ['Memory Threat Detection Alert: Windows.Ransomware.Sodinokibi'], - 'process.parent.args': [ - 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', - ], - '@timestamp': ['2024-05-07T12:48:44.975Z'], - 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], - 'host.risk.calculated_level': ['High'], - 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-20T23:38:25.169Z'], - }, - sort: [99, 1715086124975], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'cdf3b5510bb5ed622e8cefd1ce6bedc52bdd99a4c1ead537af0603469e713c8b', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'file.path': ['C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll'], - 'process.hash.md5': ['4bfef0b578515c16b9582e32b78d2594'], - 'event.category': ['malware', 'intrusion_detection', 'library'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': ['C:\\Programdata\\Q3C7N1V8.exe'], - 'process.parent.name': ['Q3C7N1V8.exe'], - 'user.name': ['Administrator'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '70d21cbdc527559c4931421e66aa819b86d5af5535445ace467e74518164c46a', - ], - 'process.pid': [7824], - 'process.code_signature.exists': [true], - 'process.parent.code_signature.exists': [false], - 'process.pe.original_file_name': ['RUNDLL32.EXE'], - 'event.module': ['endpoint'], - 'process.code_signature.subject_name': ['Microsoft Windows'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'file.hash.sha256': ['12e6642cf6413bdf5388bee663080fa299591b2ba023d069286f3be9647547c8'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Malware Detection Alert'], - 'host.name': ['SRVWIN01'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\SysWOW64\\rundll32.exe'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.executable': ['C:\\ProgramData\\Q3C7N1V8.exe'], - 'kibana.alert.workflow_status': ['open'], - 'file.name': ['cdnver.dll'], - 'process.args': [ - 'C:\\Windows\\System32\\rundll32.exe', - 'C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll,#1', - ], - 'process.code_signature.status': ['trusted'], - message: ['Malware Detection Alert'], - 'process.parent.args_count': [1], - 'process.name': ['rundll32.exe'], - 'process.parent.args': ['C:\\Programdata\\Q3C7N1V8.exe'], - '@timestamp': ['2024-05-07T12:47:32.838Z'], - 'process.command_line': [ - '"C:\\Windows\\System32\\rundll32.exe" "C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll",#1', - ], - 'host.risk.calculated_level': ['High'], - _id: ['cdf3b5510bb5ed622e8cefd1ce6bedc52bdd99a4c1ead537af0603469e713c8b'], - 'process.hash.sha1': ['9b16507aaf10a0aafa0df2ba83e8eb2708d83a02'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-16T01:51:26.472Z'], - }, - sort: [99, 1715086052838], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '6abe81eb6350fb08031761be029e7ab19f7e577a7c17a9c5ea1ed010ba1620e3', - _score: null, - fields: { - 'kibana.alert.severity': ['critical'], - 'process.hash.md5': ['4bfef0b578515c16b9582e32b78d2594'], - 'event.category': ['malware', 'intrusion_detection'], - 'host.risk.calculated_score_norm': [73.02488], - 'process.parent.command_line': ['C:\\Programdata\\Q3C7N1V8.exe'], - 'process.parent.name': ['Q3C7N1V8.exe'], - 'user.risk.calculated_level': ['High'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.hash.sha256': [ - '70d21cbdc527559c4931421e66aa819b86d5af5535445ace467e74518164c46a', - ], - 'process.pid': [7824], - 'process.code_signature.exists': [true], - 'process.code_signature.subject_name': ['Microsoft Windows'], - 'host.os.version': ['21H2 (10.0.20348.1366)'], - 'kibana.alert.risk_score': [99], - 'user.risk.calculated_score_norm': [82.16188], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': [ - 'Malicious Behavior Detection Alert: RunDLL32 with Unusual Arguments', - ], - 'host.name': ['SRVWIN01'], - 'event.outcome': ['success'], - 'process.code_signature.trusted': [true], - 'kibana.alert.workflow_status': ['open'], - 'rule.name': ['RunDLL32 with Unusual Arguments'], - 'threat.tactic.id': ['TA0005'], - 'threat.tactic.name': ['Defense Evasion'], - 'threat.technique.id': ['T1218'], - 'process.parent.args_count': [1], - 'threat.technique.subtechnique.reference': [ - 'https://attack.mitre.org/techniques/T1218/011/', - ], - 'process.name': ['rundll32.exe'], - 'threat.technique.subtechnique.name': ['Rundll32'], - _id: ['6abe81eb6350fb08031761be029e7ab19f7e577a7c17a9c5ea1ed010ba1620e3'], - 'threat.technique.name': ['System Binary Proxy Execution'], - 'threat.tactic.reference': ['https://attack.mitre.org/tactics/TA0005/'], - 'user.name': ['Administrator'], - 'threat.framework': ['MITRE ATT&CK'], - 'process.working_directory': ['C:\\Users\\Administrator\\Documents\\'], - 'process.pe.original_file_name': ['RUNDLL32.EXE'], - 'event.module': ['endpoint'], - 'user.domain': ['OMM-WIN-DETECT'], - 'process.executable': ['C:\\Windows\\SysWOW64\\rundll32.exe'], - 'process.Ext.token.integrity_level_name': ['high'], - 'process.parent.executable': ['C:\\ProgramData\\Q3C7N1V8.exe'], - 'process.args': [ - 'C:\\Windows\\System32\\rundll32.exe', - 'C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll,#1', - ], - 'process.code_signature.status': ['trusted'], - message: ['Malicious Behavior Detection Alert: RunDLL32 with Unusual Arguments'], - 'process.parent.args': ['C:\\Programdata\\Q3C7N1V8.exe'], - '@timestamp': ['2024-05-07T12:47:32.836Z'], - 'threat.technique.subtechnique.id': ['T1218.011'], - 'threat.technique.reference': ['https://attack.mitre.org/techniques/T1218/'], - 'process.command_line': [ - '"C:\\Windows\\System32\\rundll32.exe" "C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll",#1', - ], - 'host.risk.calculated_level': ['High'], - 'process.hash.sha1': ['9b16507aaf10a0aafa0df2ba83e8eb2708d83a02'], - 'event.dataset': ['endpoint.alerts'], - 'kibana.alert.original_time': ['2023-01-16T01:51:26.348Z'], - }, - sort: [99, 1715086052836], - }, - ], - }, -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts deleted file mode 100644 index a40dde44f8d67..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { GraphState } from '../../../../types'; - -export const discardPreviousGenerations = ({ - generationAttempts, - hallucinationFailures, - isHallucinationDetected, - state, -}: { - generationAttempts: number; - hallucinationFailures: number; - isHallucinationDetected: boolean; - state: GraphState; -}): GraphState => { - return { - ...state, - combinedGenerations: '', // <-- reset the combined generations - generationAttempts: generationAttempts + 1, - generations: [], // <-- reset the generations - hallucinationFailures: isHallucinationDetected - ? hallucinationFailures + 1 - : hallucinationFailures, - }; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts deleted file mode 100644 index d92d935053577..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// NOTE: we ask the LLM to `provide insights`. We do NOT use the feature name, `AttackDiscovery`, in the prompt. -export const getAlertsContextPrompt = ({ - anonymizedAlerts, - attackDiscoveryPrompt, -}: { - anonymizedAlerts: string[]; - attackDiscoveryPrompt: string; -}) => `${attackDiscoveryPrompt} - -Use context from the following alerts to provide insights: - -""" -${anonymizedAlerts.join('\n\n')} -""" -`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts deleted file mode 100644 index fb7cf6bd59f98..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { GraphState } from '../../../../types'; - -export const getAnonymizedAlertsFromState = (state: GraphState): string[] => - state.anonymizedAlerts.map((doc) => doc.pageContent); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts deleted file mode 100644 index face2a6afc6bc..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscovery } from '@kbn/elastic-assistant-common'; - -import { getMaxRetriesReached } from '../../../../helpers/get_max_retries_reached'; - -export const getUseUnrefinedResults = ({ - generationAttempts, - maxGenerationAttempts, - unrefinedResults, -}: { - generationAttempts: number; - maxGenerationAttempts: number; - unrefinedResults: AttackDiscovery[] | null; -}): boolean => { - const nextAttemptWouldExcedLimit = getMaxRetriesReached({ - generationAttempts: generationAttempts + 1, // + 1, because we just used an attempt - maxGenerationAttempts, - }); - - return nextAttemptWouldExcedLimit && unrefinedResults != null && unrefinedResults.length > 0; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts deleted file mode 100644 index 1fcd81622f0fe..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import type { Logger } from '@kbn/core/server'; - -import { discardPreviousGenerations } from './helpers/discard_previous_generations'; -import { extractJson } from '../helpers/extract_json'; -import { getAnonymizedAlertsFromState } from './helpers/get_anonymized_alerts_from_state'; -import { getChainWithFormatInstructions } from '../helpers/get_chain_with_format_instructions'; -import { getCombined } from '../helpers/get_combined'; -import { getCombinedAttackDiscoveryPrompt } from '../helpers/get_combined_attack_discovery_prompt'; -import { generationsAreRepeating } from '../helpers/generations_are_repeating'; -import { getUseUnrefinedResults } from './helpers/get_use_unrefined_results'; -import { parseCombinedOrThrow } from '../helpers/parse_combined_or_throw'; -import { responseIsHallucinated } from '../helpers/response_is_hallucinated'; -import type { GraphState } from '../../types'; - -export const getGenerateNode = ({ - llm, - logger, -}: { - llm: ActionsClientLlm; - logger?: Logger; -}): ((state: GraphState) => Promise<GraphState>) => { - const generate = async (state: GraphState): Promise<GraphState> => { - logger?.debug(() => `---GENERATE---`); - - const anonymizedAlerts: string[] = getAnonymizedAlertsFromState(state); - - const { - attackDiscoveryPrompt, - combinedGenerations, - generationAttempts, - generations, - hallucinationFailures, - maxGenerationAttempts, - maxRepeatedGenerations, - } = state; - - let combinedResponse = ''; // mutable, because it must be accessed in the catch block - let partialResponse = ''; // mutable, because it must be accessed in the catch block - - try { - const query = getCombinedAttackDiscoveryPrompt({ - anonymizedAlerts, - attackDiscoveryPrompt, - combinedMaybePartialResults: combinedGenerations, - }); - - const { chain, formatInstructions, llmType } = getChainWithFormatInstructions(llm); - - logger?.debug( - () => `generate node is invoking the chain (${llmType}), attempt ${generationAttempts}` - ); - - const rawResponse = (await chain.invoke({ - format_instructions: formatInstructions, - query, - })) as unknown as string; - - // LOCAL MUTATION: - partialResponse = extractJson(rawResponse); // remove the surrounding ```json``` - - // if the response is hallucinated, discard previous generations and start over: - if (responseIsHallucinated(partialResponse)) { - logger?.debug( - () => - `generate node detected a hallucination (${llmType}), on attempt ${generationAttempts}; discarding the accumulated generations and starting over` - ); - - return discardPreviousGenerations({ - generationAttempts, - hallucinationFailures, - isHallucinationDetected: true, - state, - }); - } - - // if the generations are repeating, discard previous generations and start over: - if ( - generationsAreRepeating({ - currentGeneration: partialResponse, - previousGenerations: generations, - sampleLastNGenerations: maxRepeatedGenerations, - }) - ) { - logger?.debug( - () => - `generate node detected (${llmType}), detected ${maxRepeatedGenerations} repeated generations on attempt ${generationAttempts}; discarding the accumulated results and starting over` - ); - - // discard the accumulated results and start over: - return discardPreviousGenerations({ - generationAttempts, - hallucinationFailures, - isHallucinationDetected: false, - state, - }); - } - - // LOCAL MUTATION: - combinedResponse = getCombined({ combinedGenerations, partialResponse }); // combine the new response with the previous ones - - const unrefinedResults = parseCombinedOrThrow({ - combinedResponse, - generationAttempts, - llmType, - logger, - nodeName: 'generate', - }); - - // use the unrefined results if we already reached the max number of retries: - const useUnrefinedResults = getUseUnrefinedResults({ - generationAttempts, - maxGenerationAttempts, - unrefinedResults, - }); - - if (useUnrefinedResults) { - logger?.debug( - () => - `generate node is using unrefined results response (${llm._llmType()}) from attempt ${generationAttempts}, because all attempts have been used` - ); - } - - return { - ...state, - attackDiscoveries: useUnrefinedResults ? unrefinedResults : null, // optionally skip the refinement step by returning the final answer - combinedGenerations: combinedResponse, - generationAttempts: generationAttempts + 1, - generations: [...generations, partialResponse], - unrefinedResults, - }; - } catch (error) { - const parsingError = `generate node is unable to parse (${llm._llmType()}) response from attempt ${generationAttempts}; (this may be an incomplete response from the model): ${error}`; - logger?.debug(() => parsingError); // logged at debug level because the error is expected when the model returns an incomplete response - - return { - ...state, - combinedGenerations: combinedResponse, - errors: [...state.errors, parsingError], - generationAttempts: generationAttempts + 1, - generations: [...generations, partialResponse], - }; - } - }; - - return generate; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts deleted file mode 100644 index 05210799f151c..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * 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 { z } from '@kbn/zod'; - -export const SYNTAX = '{{ field.name fieldValue1 fieldValue2 fieldValueN }}'; -const GOOD_SYNTAX_EXAMPLES = - 'Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }}'; - -const BAD_SYNTAX_EXAMPLES = - 'Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}'; - -const RECONNAISSANCE = 'Reconnaissance'; -const INITIAL_ACCESS = 'Initial Access'; -const EXECUTION = 'Execution'; -const PERSISTENCE = 'Persistence'; -const PRIVILEGE_ESCALATION = 'Privilege Escalation'; -const DISCOVERY = 'Discovery'; -const LATERAL_MOVEMENT = 'Lateral Movement'; -const COMMAND_AND_CONTROL = 'Command and Control'; -const EXFILTRATION = 'Exfiltration'; - -const MITRE_ATTACK_TACTICS = [ - RECONNAISSANCE, - INITIAL_ACCESS, - EXECUTION, - PERSISTENCE, - PRIVILEGE_ESCALATION, - DISCOVERY, - LATERAL_MOVEMENT, - COMMAND_AND_CONTROL, - EXFILTRATION, -] as const; - -export const AttackDiscoveriesGenerationSchema = z.object({ - insights: z - .array( - z.object({ - alertIds: z.string().array().describe(`The alert IDs that the insight is based on.`), - detailsMarkdown: z - .string() - .describe( - `A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` - ), - entitySummaryMarkdown: z - .string() - .optional() - .describe( - `A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same ${SYNTAX} syntax` - ), - mitreAttackTactics: z - .string() - .array() - .optional() - .describe( - `An array of MITRE ATT&CK tactic for the insight, using one of the following values: ${MITRE_ATTACK_TACTICS.join( - ',' - )}` - ), - summaryMarkdown: z - .string() - .describe(`A markdown summary of insight, using the same ${SYNTAX} syntax`), - title: z - .string() - .describe( - 'A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.' - ), - }) - ) - .describe( - `Insights with markdown that always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` - ), -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts deleted file mode 100644 index fd824709f5fcf..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const addTrailingBackticksIfNecessary = (text: string): string => { - const leadingJSONpattern = /^\w*```json(.*?)/s; - const trailingBackticksPattern = /(.*?)```\w*$/s; - - const hasLeadingJSONWrapper = leadingJSONpattern.test(text); - const hasTrailingBackticks = trailingBackticksPattern.test(text); - - if (hasLeadingJSONWrapper && !hasTrailingBackticks) { - return `${text}\n\`\`\``; - } - - return text; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts deleted file mode 100644 index 5e13ec9f0dafe..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { extractJson } from '.'; - -describe('extractJson', () => { - it('returns the JSON text surrounded by ```json and ``` with no whitespace or additional text', () => { - const input = '```json{"key": "value"}```'; - - const expected = '{"key": "value"}'; - - expect(extractJson(input)).toBe(expected); - }); - - it('returns the JSON block when surrounded by additional text and whitespace', () => { - const input = - 'You asked for some JSON, here it is:\n```json\n{"key": "value"}\n```\nI hope that works for you.'; - - const expected = '{"key": "value"}'; - - expect(extractJson(input)).toBe(expected); - }); - - it('returns the original text if no JSON block is found', () => { - const input = "There's no JSON here, just some text."; - - expect(extractJson(input)).toBe(input); - }); - - it('trims leading and trailing whitespace from the extracted JSON', () => { - const input = 'Text before\n```json\n {"key": "value"} \n```\nText after'; - - const expected = '{"key": "value"}'; - - expect(extractJson(input)).toBe(expected); - }); - - it('handles incomplete JSON blocks with no trailing ```', () => { - const input = 'Text before\n```json\n{"key": "value"'; // <-- no closing ```, because incomplete generation - - expect(extractJson(input)).toBe('{"key": "value"'); - }); - - it('handles multiline json (real world edge case)', () => { - const input = - '```json\n{\n "insights": [\n {\n "alertIds": [\n "a609473a23b3a66a40f2bba06795c28a0c12863c6931f39e472d069f5600cbae",\n "04a9ded2b4f10ea407711f0010d426ad328eea43ae53e1e0bf166c058947dff6",\n "8d53b9838181299b3c0b1544ea469216d72ad2234a1cce44017dd248a08d78d1",\n "51d0080ffcc1982dbae7c31a9a021f7b51422000dec1f0e0bb58bd61d934c893",\n "d93302956bee58d538f6f7a6cbf944e549e8466dacfb554a302dce46a069eef0",\n "75c89f679397f089716034cde20f5547a2e6bdd1606b1e002e0976ab339c4cd9",\n "5d8e9427c0ecc4daa5809bfe250b9a382c53e81e8f39eec87499d28efdda9300",\n "f18ac1874f510fd3fabb0ae48d0714f4952b294496ef1d993e3eb03f839e2d83",\n "e37cb31213c4c4e80beaf9f75e7966f88cdd86a228c6cb1a28e46356410fa78f",\n "cf70077b8888e8fbe434808fddbaf65d97fff244bb185a595cf0ad487e9c5850",\n "01bea609f0880b10b7b3c6cf6e8245ef0f134386fdcbf2a167e72487e0bda616",\n "289621edc88fd8b4775c541e46bcfdea40538291266179c59a5ca5afbee74cfc",\n "ba121c2045058b62a92e6a3abadd3c78a005b89129630e2271b2f45d5fd995b2",\n "fceb940b252be079df3629550d852bd2793f79071c917227268fa1b805abc8d1",\n "7044589c27bab148cdb97d9e2eeb88bd924fca82a6a05a53ec94dcadf8e56303",\n "1b68be35429f52280456aab17dd94191fe5c47fed9768f00d9f9e9044a08bbb5",\n "52478d4a119bbc44bec67f384f83dfa20b33cf9963177e619cd47a32bababe12",\n "fecbbb8924493b466e8f5744e0875a9ee91f326213b691b576b13da3fb875ebf",\n "c46bbdeb7b59f52c976e7e4f30e3d5c65f417d716cb140096b5edba52b1449a1",\n "f12caebcbda087fc8b49cdced64a8997dd1428f4cf91ebb251434a55126399b3",\n "c7478edbd13af443cfafc57d50e5206c5ae8c0f9c9cabc073fdc2d3946559617",\n "3585ae62651929ef405f9783410d7a94f4254d299205e22f22966178f189bb11",\n "f50f531912af1d31a66a0e37d4c0d9c571c2cca6bef2c7f8453eb3ab67c4d1a0",\n "95a9403f0bb97d03fc3c2eb06386503831766f541b736468088092c5e0e25830",\n "c1292c67f3ccd2cb2651c601f0816122cfa459276fa5fc89b40c62d1a793963e",\n "8911886e1b2964176f70eaee2aa6693ce101ee9c8ec5434acdc7ff18616ec31c",\n "bfbfb02c03c6f69fc2352c48d8fd7c7e4b557c611e16956fbb63e337a513e699",\n "064cbdc1932029fcb34f6ba685211b971afde3b8aa4325054bedaf4c9e4587ed",\n "9fd5d0ca9b9fff6e37f1114ad874103badb2b0570ef143cd4a26a553effdff00",\n "9e2687f26f04b5a8def3266f89fbe7217da2d4355c3b035268df1802f1342c81",\n "64557c4006c52119c01f6e3e582ce1b8207b2e8f64aaaa630ca1fd156c01ea1e",\n "df98d2568c986d101af055f78c7e2a39299627531c28012b5025d10e2ec1b208",\n "10683db11fb21cae36577f83722c686c2fc691d2be6fc4396f2733564f3210d1",\n "f46e7b3266200e3e23b15b5acea7bb934e2c17d23058e10daeed51f036f4932b",\n "3c77d55f912b80b66cc1e4e1df02a22ddee07c50338a409374fb2567d2fb4ca3",\n "8ec169c0fdf558c0d9d9ad8dedad0898b15bb718421b4cab8f5cce4ebcb78254",\n "4119a1705f993588f8d1d576e567ec17f102aeafe535e53bb56ec833418ccd08",\n "b53d06bfd23ab843dba67e2fde0da6364475b0bfb9c40cb8a6641cc4ecadec01",\n "1dcd85c8279fd7152dadecfc547cce06261d23ef4589fe4fdcc92b1ceeb76c0f",\n "d4ed490b1d39925ee612058655030bdb7cecda3e5893e1c40dbbac852b72fbc6",\n "2ecc96c4d51f5338684c08e7c67357e504abfec6fc4f21753a3c941189db68e1",\n "0c9fb123686bc739d117ee4f607ffbcef39f1f72e7eab6d01b70bbb40480b3d6",\n "162be5e04f54a5cd475d2437fe769ee044324b0a32ce83a735f61719b8b5fd63",\n "21eae60b4b29f7f01cc7006372374e1c5d6912858c33397cdbe4470df97fba79",\n "0409539590b6d9b80f7071d3d5658434f982ba7957aa6a5037f8b7a73b70100d",\n "5e8e654df34a9053f8b90e4ade25520dbee5994ebf7da531e1e7255d029ab031",\n "3ef381b2d29d71bc3ac8580d333344948a2664855a89ff037299a8b4aa663293",\n "0aef1fe2506842f9c53549049b47a8166bcc3d6efe2d8fcf1e57f3a634ed137c",\n "c2d12dacd0cd6ef4a7386c8d0146d3eb91a7e1e9f2d8d47bffaab07a92577993",\n "45e6663c65172e225e2531df3dce58096ed6e9a7d0fd7819e5b6f094a41731a0",\n "f2af064d46f1db1d96c7c9508a462993851e42f29566f2101ea3a1c51e5e451c",\n "b75c046d06f86eea41826999211ab5e6c9cb5fe067ade561fb5dc5f0b52d4584",\n "1fb9fbb26b78c2e9c56abf8e39e4cb278a5a382d53115dcb1624fdefca762865",\n "d78c4d12f6d50278be6320df1fe10beeef8723558cdb12d9d6c7d1aa8180498b",\n "c8fa7d3a31906893c47df234318e94bc4371b55ac54edc60b2c09afd8a9291c6",\n "5236dc9c55f19d8aed50078cc6ecd1de85041afa65003276fc311c14d5a74d0a",\n "efb9d548ff94246a22cfa8e06b70689d8f3edf69c8ad45c3811e0d340b4b10ff",\n "842c8d78d995f49b569934cf5e8316ba1d93a1d73e757210d5f0eb7e1ed52049",\n "b95dcfba35d31ab263bfab939280c71893bdb39e3a744c2f3cc38612ebcbb42a",\n "d6387171a203c64fd1c09514a028cf813d2ffccf968831c92cdf22287992e004",\n "b8d098f358ce5e8fa2900ac18435078652353a32a19ef2fd038bf82eee3a0731"\n ],\n "detailsMarkdown": "### Attack Progression\\n- **Initial Access**: The attack began with a spearphishing attachment delivered via Microsoft Office documents. The documents contained malicious macros that executed upon opening.\\n- **Execution**: The malicious macros executed various commands, including the use of `certutil` to decode and execute payloads, and `regsvr32` to register malicious DLLs.\\n- **Persistence**: The attackers established persistence by modifying registry run keys and creating scheduled tasks.\\n- **Credential Access**: The attackers attempted to capture credentials using `osascript` on macOS systems.\\n- **Defense Evasion**: The attackers used code signing with invalid or expired certificates to evade detection.\\n- **Command and Control**: The attackers established command and control channels using various techniques, including the use of `mshta` and `powershell` scripts.\\n- **Exfiltration**: The attackers exfiltrated data using tools like `curl` to transfer data to remote servers.\\n- **Impact**: The attackers deployed ransomware, including `Sodinokibi` and `Bumblebee`, to encrypt files and demand ransom payments.\\n\\n### Affected Hosts and Users\\n- **Hosts**: Multiple hosts across different operating systems (Windows, macOS, Linux) were affected.\\n- **Users**: The attacks targeted various users, including administrators and regular users.\\n\\n### Known Threat Groups\\n- The attack patterns and techniques used in this campaign are consistent with those employed by known threat groups such as `Emotet`, `Qbot`, and `Sodinokibi`.\\n\\n### Recommendations\\n- **Immediate Actions**: Isolate affected systems, reset passwords, and review network traffic for signs of command and control communications.\\n- **Long-term Actions**: Implement multi-factor authentication, conduct regular security awareness training, and deploy advanced endpoint protection solutions.",\n "entitySummaryMarkdown": "{{ host.name 9ed6a9db-da4d-4877-a2b4-f7a22cc55e9a }} {{ user.name c45d8d76-bff6-4c4b-aa5a-62eb15d68adb }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence",\n "Credential Access",\n "Defense Evasion",\n "Command and Control",\n "Exfiltration",\n "Impact"\n ],\n "summaryMarkdown": "A sophisticated multi-stage attack was detected, involving spearphishing, credential access, and ransomware deployment. The attack targeted multiple hosts and users across different operating systems.",\n "title": "Multi-Stage Cyber Attack Detected"\n }\n ]\n}\n```'; - - const expected = - '{\n "insights": [\n {\n "alertIds": [\n "a609473a23b3a66a40f2bba06795c28a0c12863c6931f39e472d069f5600cbae",\n "04a9ded2b4f10ea407711f0010d426ad328eea43ae53e1e0bf166c058947dff6",\n "8d53b9838181299b3c0b1544ea469216d72ad2234a1cce44017dd248a08d78d1",\n "51d0080ffcc1982dbae7c31a9a021f7b51422000dec1f0e0bb58bd61d934c893",\n "d93302956bee58d538f6f7a6cbf944e549e8466dacfb554a302dce46a069eef0",\n "75c89f679397f089716034cde20f5547a2e6bdd1606b1e002e0976ab339c4cd9",\n "5d8e9427c0ecc4daa5809bfe250b9a382c53e81e8f39eec87499d28efdda9300",\n "f18ac1874f510fd3fabb0ae48d0714f4952b294496ef1d993e3eb03f839e2d83",\n "e37cb31213c4c4e80beaf9f75e7966f88cdd86a228c6cb1a28e46356410fa78f",\n "cf70077b8888e8fbe434808fddbaf65d97fff244bb185a595cf0ad487e9c5850",\n "01bea609f0880b10b7b3c6cf6e8245ef0f134386fdcbf2a167e72487e0bda616",\n "289621edc88fd8b4775c541e46bcfdea40538291266179c59a5ca5afbee74cfc",\n "ba121c2045058b62a92e6a3abadd3c78a005b89129630e2271b2f45d5fd995b2",\n "fceb940b252be079df3629550d852bd2793f79071c917227268fa1b805abc8d1",\n "7044589c27bab148cdb97d9e2eeb88bd924fca82a6a05a53ec94dcadf8e56303",\n "1b68be35429f52280456aab17dd94191fe5c47fed9768f00d9f9e9044a08bbb5",\n "52478d4a119bbc44bec67f384f83dfa20b33cf9963177e619cd47a32bababe12",\n "fecbbb8924493b466e8f5744e0875a9ee91f326213b691b576b13da3fb875ebf",\n "c46bbdeb7b59f52c976e7e4f30e3d5c65f417d716cb140096b5edba52b1449a1",\n "f12caebcbda087fc8b49cdced64a8997dd1428f4cf91ebb251434a55126399b3",\n "c7478edbd13af443cfafc57d50e5206c5ae8c0f9c9cabc073fdc2d3946559617",\n "3585ae62651929ef405f9783410d7a94f4254d299205e22f22966178f189bb11",\n "f50f531912af1d31a66a0e37d4c0d9c571c2cca6bef2c7f8453eb3ab67c4d1a0",\n "95a9403f0bb97d03fc3c2eb06386503831766f541b736468088092c5e0e25830",\n "c1292c67f3ccd2cb2651c601f0816122cfa459276fa5fc89b40c62d1a793963e",\n "8911886e1b2964176f70eaee2aa6693ce101ee9c8ec5434acdc7ff18616ec31c",\n "bfbfb02c03c6f69fc2352c48d8fd7c7e4b557c611e16956fbb63e337a513e699",\n "064cbdc1932029fcb34f6ba685211b971afde3b8aa4325054bedaf4c9e4587ed",\n "9fd5d0ca9b9fff6e37f1114ad874103badb2b0570ef143cd4a26a553effdff00",\n "9e2687f26f04b5a8def3266f89fbe7217da2d4355c3b035268df1802f1342c81",\n "64557c4006c52119c01f6e3e582ce1b8207b2e8f64aaaa630ca1fd156c01ea1e",\n "df98d2568c986d101af055f78c7e2a39299627531c28012b5025d10e2ec1b208",\n "10683db11fb21cae36577f83722c686c2fc691d2be6fc4396f2733564f3210d1",\n "f46e7b3266200e3e23b15b5acea7bb934e2c17d23058e10daeed51f036f4932b",\n "3c77d55f912b80b66cc1e4e1df02a22ddee07c50338a409374fb2567d2fb4ca3",\n "8ec169c0fdf558c0d9d9ad8dedad0898b15bb718421b4cab8f5cce4ebcb78254",\n "4119a1705f993588f8d1d576e567ec17f102aeafe535e53bb56ec833418ccd08",\n "b53d06bfd23ab843dba67e2fde0da6364475b0bfb9c40cb8a6641cc4ecadec01",\n "1dcd85c8279fd7152dadecfc547cce06261d23ef4589fe4fdcc92b1ceeb76c0f",\n "d4ed490b1d39925ee612058655030bdb7cecda3e5893e1c40dbbac852b72fbc6",\n "2ecc96c4d51f5338684c08e7c67357e504abfec6fc4f21753a3c941189db68e1",\n "0c9fb123686bc739d117ee4f607ffbcef39f1f72e7eab6d01b70bbb40480b3d6",\n "162be5e04f54a5cd475d2437fe769ee044324b0a32ce83a735f61719b8b5fd63",\n "21eae60b4b29f7f01cc7006372374e1c5d6912858c33397cdbe4470df97fba79",\n "0409539590b6d9b80f7071d3d5658434f982ba7957aa6a5037f8b7a73b70100d",\n "5e8e654df34a9053f8b90e4ade25520dbee5994ebf7da531e1e7255d029ab031",\n "3ef381b2d29d71bc3ac8580d333344948a2664855a89ff037299a8b4aa663293",\n "0aef1fe2506842f9c53549049b47a8166bcc3d6efe2d8fcf1e57f3a634ed137c",\n "c2d12dacd0cd6ef4a7386c8d0146d3eb91a7e1e9f2d8d47bffaab07a92577993",\n "45e6663c65172e225e2531df3dce58096ed6e9a7d0fd7819e5b6f094a41731a0",\n "f2af064d46f1db1d96c7c9508a462993851e42f29566f2101ea3a1c51e5e451c",\n "b75c046d06f86eea41826999211ab5e6c9cb5fe067ade561fb5dc5f0b52d4584",\n "1fb9fbb26b78c2e9c56abf8e39e4cb278a5a382d53115dcb1624fdefca762865",\n "d78c4d12f6d50278be6320df1fe10beeef8723558cdb12d9d6c7d1aa8180498b",\n "c8fa7d3a31906893c47df234318e94bc4371b55ac54edc60b2c09afd8a9291c6",\n "5236dc9c55f19d8aed50078cc6ecd1de85041afa65003276fc311c14d5a74d0a",\n "efb9d548ff94246a22cfa8e06b70689d8f3edf69c8ad45c3811e0d340b4b10ff",\n "842c8d78d995f49b569934cf5e8316ba1d93a1d73e757210d5f0eb7e1ed52049",\n "b95dcfba35d31ab263bfab939280c71893bdb39e3a744c2f3cc38612ebcbb42a",\n "d6387171a203c64fd1c09514a028cf813d2ffccf968831c92cdf22287992e004",\n "b8d098f358ce5e8fa2900ac18435078652353a32a19ef2fd038bf82eee3a0731"\n ],\n "detailsMarkdown": "### Attack Progression\\n- **Initial Access**: The attack began with a spearphishing attachment delivered via Microsoft Office documents. The documents contained malicious macros that executed upon opening.\\n- **Execution**: The malicious macros executed various commands, including the use of `certutil` to decode and execute payloads, and `regsvr32` to register malicious DLLs.\\n- **Persistence**: The attackers established persistence by modifying registry run keys and creating scheduled tasks.\\n- **Credential Access**: The attackers attempted to capture credentials using `osascript` on macOS systems.\\n- **Defense Evasion**: The attackers used code signing with invalid or expired certificates to evade detection.\\n- **Command and Control**: The attackers established command and control channels using various techniques, including the use of `mshta` and `powershell` scripts.\\n- **Exfiltration**: The attackers exfiltrated data using tools like `curl` to transfer data to remote servers.\\n- **Impact**: The attackers deployed ransomware, including `Sodinokibi` and `Bumblebee`, to encrypt files and demand ransom payments.\\n\\n### Affected Hosts and Users\\n- **Hosts**: Multiple hosts across different operating systems (Windows, macOS, Linux) were affected.\\n- **Users**: The attacks targeted various users, including administrators and regular users.\\n\\n### Known Threat Groups\\n- The attack patterns and techniques used in this campaign are consistent with those employed by known threat groups such as `Emotet`, `Qbot`, and `Sodinokibi`.\\n\\n### Recommendations\\n- **Immediate Actions**: Isolate affected systems, reset passwords, and review network traffic for signs of command and control communications.\\n- **Long-term Actions**: Implement multi-factor authentication, conduct regular security awareness training, and deploy advanced endpoint protection solutions.",\n "entitySummaryMarkdown": "{{ host.name 9ed6a9db-da4d-4877-a2b4-f7a22cc55e9a }} {{ user.name c45d8d76-bff6-4c4b-aa5a-62eb15d68adb }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence",\n "Credential Access",\n "Defense Evasion",\n "Command and Control",\n "Exfiltration",\n "Impact"\n ],\n "summaryMarkdown": "A sophisticated multi-stage attack was detected, involving spearphishing, credential access, and ransomware deployment. The attack targeted multiple hosts and users across different operating systems.",\n "title": "Multi-Stage Cyber Attack Detected"\n }\n ]\n}'; - - expect(extractJson(input)).toBe(expected); - }); - - it('handles "Here is my analysis of the security events in JSON format" (real world edge case)', () => { - const input = - 'Here is my analysis of the security events in JSON format:\n\n```json\n{\n "insights": [\n {\n "alertIds": [\n "d776c8406fd81427b1f166550ac1b949017da7a13dc734594e4b05f24622b26e",\n "504c012054cfe91986311b4e6bc8523914434fab590e5c07c0328fab6566753c",\n "b706b8c19e68cc4f54b69f0a93e32b10f4102b610213b7826fb1d303b90a0536",\n "7763ebe716c47f64987362a9fb120d73873c77d26ad915f2c3d57c5dd3b7eed0",\n "25c61e0423a9bfd7f268ca6e9b67d4f507207c0cb1e1b4701aa5248cb3866f1f",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:17.566Z }}, a malicious file with SHA256 hash {{ file.hash.sha256 74ef6cc38f5a1a80148752b63c117e6846984debd2af806c65887195a8eccc56 }} was detected on {{ host.name SRVNIX05 }}\\n- The file was initially downloaded as a zip archive and extracted to /home/ubuntu/\\n- The malware, identified as Linux.Trojan.BPFDoor, was then copied to /dev/shm/kdmtmpflush and executed\\n- This trojan allows remote attackers to gain backdoor access to the compromised Linux system\\n- The malware was executed with root privileges, indicating a serious compromise\\n- Network connections and other malicious activities from this backdoor should be investigated",\n "entitySummaryMarkdown": "{{ host.name SRVNIX05 }} compromised by Linux.Trojan.BPFDoor malware executed as {{ user.name root }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Linux.Trojan.BPFDoor malware detected and executed on {{ host.name SRVNIX05 }} with root privileges, allowing remote backdoor access",\n "title": "Linux Trojan BPFDoor Backdoor Detected"\n },\n {\n "alertIds": [\n "5946b409f49b0983de53e575db0874ef11b0544766f816dc702941a69a9b0dd1",\n "aa0ba23872c48a8ee761591c5bb0a9ed8258c51b27111cc72dbe8624a0b7da96",\n "b60a5c344b579cab9406becdec14a11d56f4eccc2bf6caaf6eb72ddf1707124c",\n "4920ca19a22968e4ab0cf299974234699d9cce15545c401a2b8fd09d71f6e106",\n "26302b2afbe58c8dcfde950c7164262c626af0b85f0808f3d8632b1d6a406d16",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "41564c953dd101b942537110d175d2b269959c24dbf5b7c482e32851ab6f5dc1",\n "12e102970920f5f938b21effb09394c00540075fc4057ec79e221046a8b6ba0f"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:33.570Z }}, suspicious activity was detected on {{ host.name SRVMAC08 }}\\n- A malicious application \\"My Go Application.app\\" was executed, likely masquerading as a legitimate program\\n- The malware attempted to access the user\'s keychain to steal credentials\\n- It executed a file named {{ file.name unix1 }} which tried to access {{ file.path /Users/james/library/Keychains/login.keychain-db }}\\n- The malware also attempted to display a fake system preferences dialog to phish the user\'s password\\n- This attack targeted {{ user.name james }}, who has high user criticality",\n "entitySummaryMarkdown": "{{ host.name SRVMAC08 }} infected with malware targeting {{ user.name james }}\'s credentials",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Credential Access" \n ],\n "summaryMarkdown": "Malware on {{ host.name SRVMAC08 }} attempted to steal keychain credentials and phish password from {{ user.name james }}",\n "title": "macOS Credential Theft Attempt"\n },\n {\n "alertIds": [\n "a492cd3202717d0c86f9b44623b12ac4d19855722e0fadb2f84a547afb45871a",\n "7fdf3a399b0a6df74784f478c2712a0e47ff997f73701593b3a5a56fa452056f",\n "bf33e5f004b6f6f41e362f929b3fa16b5ea9ecbb0f6389acd17dfcfb67ff3ae9",\n "b6559664247c438f9cd15022feb87855253c3cef882cc52d2e064f2693977f1c",\n "636a5a24b810bf2dbc5e2417858ac218b1fadb598fa55676745f88c0509f3e48",\n "fc0f6f9939277cc4f526148c15813f5d48094e557fdcf0ba9e773b2a16ec8c2e",\n "0029a93e8f72dce05a22ca0cc5a5cd1ca8a29b93b3c8864f7623f10b98d79084",\n "67f41b973f82fc141d75fbbd1d6caba11066c19b2a1c720fcec9e681e1cfa60c",\n "79774ae772225e94b6183f5ea394572ebe24452be99100bab145173c57c73d3b"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:54.836Z }}, malicious activity was detected on {{ host.name SRVWIN01 }}\\n- An Excel file was used to drop and execute malware\\n- The malware used certutil.exe to decode a malicious payload\\n- A suspicious executable {{ file.name Q3C7N1V8.exe }} was created in C:\\\\ProgramData\\\\\\n- The malware established persistence by modifying registry run keys\\n- It then executed a DLL {{ file.name cdnver.dll }} using rundll32.exe\\n- This attack chain indicates a sophisticated malware infection, likely part of an ongoing attack campaign",\n "entitySummaryMarkdown": "{{ host.name SRVWIN01 }} infected via malicious Excel file executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access", \n "Execution",\n "Persistence",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN01 }} via malicious Excel file, establishing persistence and executing additional payloads",\n "title": "Excel-based Malware Infection Chain"\n },\n {\n "alertIds": [\n "801ec41afa5f05a7cafefe4eaff87be1f9eb7ecbfcfc501bd83a12f19e742be0",\n "eafd7577e1d88b2c4fc3d0e3eb54b2a315f79996f075ba3c57d6f2ae7181c53b",\n "eb8fee0ceacc8caec4757e95ec132a42bae4ba7841126ce9616873e01e806ddf",\n "69dcd5e48424cc8a04a965f5bec7539c8221ac556a7b93c531cdc7e02b58c191",\n "6c81da91ad4ec313c5a4aa970e1fdf7c3ee6dbfa8536c734bd12c72f1abe3a09",\n "584d904ea196623eb794df40565797656e24d05a707638447b5e53c05d520510",\n "46d05beb516dae1ad2f168084cdeb5bfd35ac1b1194bd65aa1c837fb3b77c21d",\n "c79fe367d985d9a5d9ee723ce94977b88fe1bbb3ec8e2ffbb7b3ee134d6b49ef",\n "3ef6baa7c7c99cad5b7832e6a778a7d1ea2d88729a3e50fbf2b821d0e57f2740",\n "1fbe36af64b587d7604812f6a248754cfe8c1d80b0551046c1fc95640d0ba538",\n "4451f6a45edc2d90f85717925071457e88dd41d0ee3d3c377f5721a254651513",\n "7ec9f53a2c4571325476ad2f4de3d2ecb49609b35a4a30a33d8d57e815d09f52",\n "ca57fd3a83e06419ce8299eefd3c783bd3d33b46ce47ffd27e2abdcb2b3e0955"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:14.847Z }}, a malicious OneNote file was opened on {{ host.name SRVWIN04 }}\\n- The OneNote file executed an embedded HTA file using mshta.exe\\n- The HTA file then downloaded additional malware using curl.exe\\n- A suspicious DLL {{ file.path C:\\\\ProgramData\\\\121.png }} was loaded using rundll32.exe\\n- The malware injected shellcode into legitimate Windows processes like AtBroker.exe\\n- Memory scans detected signatures matching the Qbot banking trojan\\n- The malware established persistence by modifying registry run keys\\n- It also performed domain trust enumeration, indicating potential lateral movement preparation\\n- This sophisticated attack chain suggests a targeted intrusion by an advanced threat actor",\n "entitySummaryMarkdown": "{{ host.name SRVWIN04 }} compromised via malicious OneNote file opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution", \n "Persistence",\n "Defense Evasion",\n "Discovery"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN04 }} via OneNote file, downloading Qbot trojan and preparing for potential lateral movement",\n "title": "OneNote-based Qbot Infection Chain"\n },\n {\n "alertIds": [\n "7150ee5a9571c6028573bf7d9c2ed0da15c3387ee3c8f668741799496f7b4ae9",\n "6053ca3481a9307d3a8626fe055357541bb53d97f5deb1b7b346ec86441c335b",\n "d9c3908a4ac46b90270e6aab8217ab6385a114574931026f1df8cfc930260ff6",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070",\n "f045dc2a57582944b6e198e685e98bf02f86b5eb23ddbbdbb015c8568867122c",\n "171fe0490d48e9cac6f5b46aec7bfa67f3ecb96af308027018ca881bae1ce5d7",\n "0e22ea9514fd663a3841a212b19736fd1579c301d80f4838f25adeec24de4cf6",\n "9d8fdb59213e5a950d93253f9f986c730c877a70493c4f47ad0de52ef50c42f1"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:58.609Z }}, a malicious executable was run on {{ host.name SRVWIN02 }}\\n- The malware injected shellcode into the legitimate MsMpEng.exe (Windows Defender) process\\n- Memory scans detected signatures matching the Sodinokibi (REvil) ransomware\\n- The malware created ransom notes and began encrypting files\\n- It also attempted to enable network discovery, likely to spread to other systems\\n- This indicates an active ransomware infection that could quickly spread across the network",\n "entitySummaryMarkdown": "{{ host.name SRVWIN02 }} infected with Sodinokibi ransomware executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion",\n "Impact"\n ],\n "summaryMarkdown": "Sodinokibi (REvil) ransomware detected on {{ host.name SRVWIN02 }}, actively encrypting files and attempting to spread",\n "title": "Active Sodinokibi Ransomware Infection"\n },\n {\n "alertIds": [\n "6f8e71d59956c6dbed5c88986cdafd4386684e3879085b2742e1f2d38b282066",\n "c13b78fbfef05ddc81c73b436ccb5288d8cd52a46175638b1b3b0d311f8b53e8",\n "b0f3d3f5bfc0b1d1f3c7e219ee44dc225fa26cafd40697073a636b44cf6054ad"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:22.077Z }}, suspicious activity was detected on {{ host.name SRVWIN06 }}\\n- The msiexec.exe process spawned an unusual PowerShell child process\\n- The PowerShell process executed a script from a suspicious temporary directory\\n- Memory scans of the PowerShell process detected signatures matching the Bumblebee malware loader\\n- Bumblebee is known to be used by multiple ransomware groups as an initial access vector\\n- This indicates a likely ongoing attack attempting to deploy additional malware or ransomware",\n "entitySummaryMarkdown": "{{ host.name SRVWIN06 }} infected with Bumblebee malware loader via {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Bumblebee malware loader detected on {{ host.name SRVWIN06 }}, likely attempting to deploy additional payloads",\n "title": "Bumblebee Malware Loader Detected"\n },\n {\n "alertIds": [\n "f629babc51c3628517d8a7e1f0662124ee41e4328b1dbcf72dc3fc6f2e410d33",\n "627d00600f803366edb83700b546a4bf486e2990ac7140d842e898eb6e298e83",\n "6181847506974ed4458f03b60919c4a306197b5cb040ab324d2d1f6d0ca5bde1",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "df26b2d23068b77fdc001ea44f46505a259f02ceccc9fa0b2401c5e35190e710",\n "9c038ff779bd0ff514a1ff2b55caa359189d8bcebc48c6ac14a789946e87eaed"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:27.839Z }}, a malicious Word document was opened on {{ host.name SRVWIN07 }}\\n- The document spawned wscript.exe to execute a malicious VBS script\\n- The VBS script then launched a PowerShell process with suspicious arguments\\n- PowerShell was used to create a scheduled task for persistence\\n- This attack chain indicates a likely attempt to establish a foothold for further malicious activities",\n "entitySummaryMarkdown": "{{ host.name SRVWIN07 }} compromised via malicious Word document opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Malicious Word document on {{ host.name SRVWIN07 }} led to execution of VBS and PowerShell scripts, establishing persistence via scheduled task",\n "title": "Malicious Document Leads to Persistence"\n }\n ]\n}'; - - const expected = - '{\n "insights": [\n {\n "alertIds": [\n "d776c8406fd81427b1f166550ac1b949017da7a13dc734594e4b05f24622b26e",\n "504c012054cfe91986311b4e6bc8523914434fab590e5c07c0328fab6566753c",\n "b706b8c19e68cc4f54b69f0a93e32b10f4102b610213b7826fb1d303b90a0536",\n "7763ebe716c47f64987362a9fb120d73873c77d26ad915f2c3d57c5dd3b7eed0",\n "25c61e0423a9bfd7f268ca6e9b67d4f507207c0cb1e1b4701aa5248cb3866f1f",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:17.566Z }}, a malicious file with SHA256 hash {{ file.hash.sha256 74ef6cc38f5a1a80148752b63c117e6846984debd2af806c65887195a8eccc56 }} was detected on {{ host.name SRVNIX05 }}\\n- The file was initially downloaded as a zip archive and extracted to /home/ubuntu/\\n- The malware, identified as Linux.Trojan.BPFDoor, was then copied to /dev/shm/kdmtmpflush and executed\\n- This trojan allows remote attackers to gain backdoor access to the compromised Linux system\\n- The malware was executed with root privileges, indicating a serious compromise\\n- Network connections and other malicious activities from this backdoor should be investigated",\n "entitySummaryMarkdown": "{{ host.name SRVNIX05 }} compromised by Linux.Trojan.BPFDoor malware executed as {{ user.name root }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Linux.Trojan.BPFDoor malware detected and executed on {{ host.name SRVNIX05 }} with root privileges, allowing remote backdoor access",\n "title": "Linux Trojan BPFDoor Backdoor Detected"\n },\n {\n "alertIds": [\n "5946b409f49b0983de53e575db0874ef11b0544766f816dc702941a69a9b0dd1",\n "aa0ba23872c48a8ee761591c5bb0a9ed8258c51b27111cc72dbe8624a0b7da96",\n "b60a5c344b579cab9406becdec14a11d56f4eccc2bf6caaf6eb72ddf1707124c",\n "4920ca19a22968e4ab0cf299974234699d9cce15545c401a2b8fd09d71f6e106",\n "26302b2afbe58c8dcfde950c7164262c626af0b85f0808f3d8632b1d6a406d16",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "41564c953dd101b942537110d175d2b269959c24dbf5b7c482e32851ab6f5dc1",\n "12e102970920f5f938b21effb09394c00540075fc4057ec79e221046a8b6ba0f"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:33.570Z }}, suspicious activity was detected on {{ host.name SRVMAC08 }}\\n- A malicious application \\"My Go Application.app\\" was executed, likely masquerading as a legitimate program\\n- The malware attempted to access the user\'s keychain to steal credentials\\n- It executed a file named {{ file.name unix1 }} which tried to access {{ file.path /Users/james/library/Keychains/login.keychain-db }}\\n- The malware also attempted to display a fake system preferences dialog to phish the user\'s password\\n- This attack targeted {{ user.name james }}, who has high user criticality",\n "entitySummaryMarkdown": "{{ host.name SRVMAC08 }} infected with malware targeting {{ user.name james }}\'s credentials",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Credential Access" \n ],\n "summaryMarkdown": "Malware on {{ host.name SRVMAC08 }} attempted to steal keychain credentials and phish password from {{ user.name james }}",\n "title": "macOS Credential Theft Attempt"\n },\n {\n "alertIds": [\n "a492cd3202717d0c86f9b44623b12ac4d19855722e0fadb2f84a547afb45871a",\n "7fdf3a399b0a6df74784f478c2712a0e47ff997f73701593b3a5a56fa452056f",\n "bf33e5f004b6f6f41e362f929b3fa16b5ea9ecbb0f6389acd17dfcfb67ff3ae9",\n "b6559664247c438f9cd15022feb87855253c3cef882cc52d2e064f2693977f1c",\n "636a5a24b810bf2dbc5e2417858ac218b1fadb598fa55676745f88c0509f3e48",\n "fc0f6f9939277cc4f526148c15813f5d48094e557fdcf0ba9e773b2a16ec8c2e",\n "0029a93e8f72dce05a22ca0cc5a5cd1ca8a29b93b3c8864f7623f10b98d79084",\n "67f41b973f82fc141d75fbbd1d6caba11066c19b2a1c720fcec9e681e1cfa60c",\n "79774ae772225e94b6183f5ea394572ebe24452be99100bab145173c57c73d3b"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:54.836Z }}, malicious activity was detected on {{ host.name SRVWIN01 }}\\n- An Excel file was used to drop and execute malware\\n- The malware used certutil.exe to decode a malicious payload\\n- A suspicious executable {{ file.name Q3C7N1V8.exe }} was created in C:\\\\ProgramData\\\\\\n- The malware established persistence by modifying registry run keys\\n- It then executed a DLL {{ file.name cdnver.dll }} using rundll32.exe\\n- This attack chain indicates a sophisticated malware infection, likely part of an ongoing attack campaign",\n "entitySummaryMarkdown": "{{ host.name SRVWIN01 }} infected via malicious Excel file executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access", \n "Execution",\n "Persistence",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN01 }} via malicious Excel file, establishing persistence and executing additional payloads",\n "title": "Excel-based Malware Infection Chain"\n },\n {\n "alertIds": [\n "801ec41afa5f05a7cafefe4eaff87be1f9eb7ecbfcfc501bd83a12f19e742be0",\n "eafd7577e1d88b2c4fc3d0e3eb54b2a315f79996f075ba3c57d6f2ae7181c53b",\n "eb8fee0ceacc8caec4757e95ec132a42bae4ba7841126ce9616873e01e806ddf",\n "69dcd5e48424cc8a04a965f5bec7539c8221ac556a7b93c531cdc7e02b58c191",\n "6c81da91ad4ec313c5a4aa970e1fdf7c3ee6dbfa8536c734bd12c72f1abe3a09",\n "584d904ea196623eb794df40565797656e24d05a707638447b5e53c05d520510",\n "46d05beb516dae1ad2f168084cdeb5bfd35ac1b1194bd65aa1c837fb3b77c21d",\n "c79fe367d985d9a5d9ee723ce94977b88fe1bbb3ec8e2ffbb7b3ee134d6b49ef",\n "3ef6baa7c7c99cad5b7832e6a778a7d1ea2d88729a3e50fbf2b821d0e57f2740",\n "1fbe36af64b587d7604812f6a248754cfe8c1d80b0551046c1fc95640d0ba538",\n "4451f6a45edc2d90f85717925071457e88dd41d0ee3d3c377f5721a254651513",\n "7ec9f53a2c4571325476ad2f4de3d2ecb49609b35a4a30a33d8d57e815d09f52",\n "ca57fd3a83e06419ce8299eefd3c783bd3d33b46ce47ffd27e2abdcb2b3e0955"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:14.847Z }}, a malicious OneNote file was opened on {{ host.name SRVWIN04 }}\\n- The OneNote file executed an embedded HTA file using mshta.exe\\n- The HTA file then downloaded additional malware using curl.exe\\n- A suspicious DLL {{ file.path C:\\\\ProgramData\\\\121.png }} was loaded using rundll32.exe\\n- The malware injected shellcode into legitimate Windows processes like AtBroker.exe\\n- Memory scans detected signatures matching the Qbot banking trojan\\n- The malware established persistence by modifying registry run keys\\n- It also performed domain trust enumeration, indicating potential lateral movement preparation\\n- This sophisticated attack chain suggests a targeted intrusion by an advanced threat actor",\n "entitySummaryMarkdown": "{{ host.name SRVWIN04 }} compromised via malicious OneNote file opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution", \n "Persistence",\n "Defense Evasion",\n "Discovery"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN04 }} via OneNote file, downloading Qbot trojan and preparing for potential lateral movement",\n "title": "OneNote-based Qbot Infection Chain"\n },\n {\n "alertIds": [\n "7150ee5a9571c6028573bf7d9c2ed0da15c3387ee3c8f668741799496f7b4ae9",\n "6053ca3481a9307d3a8626fe055357541bb53d97f5deb1b7b346ec86441c335b",\n "d9c3908a4ac46b90270e6aab8217ab6385a114574931026f1df8cfc930260ff6",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070",\n "f045dc2a57582944b6e198e685e98bf02f86b5eb23ddbbdbb015c8568867122c",\n "171fe0490d48e9cac6f5b46aec7bfa67f3ecb96af308027018ca881bae1ce5d7",\n "0e22ea9514fd663a3841a212b19736fd1579c301d80f4838f25adeec24de4cf6",\n "9d8fdb59213e5a950d93253f9f986c730c877a70493c4f47ad0de52ef50c42f1"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:58.609Z }}, a malicious executable was run on {{ host.name SRVWIN02 }}\\n- The malware injected shellcode into the legitimate MsMpEng.exe (Windows Defender) process\\n- Memory scans detected signatures matching the Sodinokibi (REvil) ransomware\\n- The malware created ransom notes and began encrypting files\\n- It also attempted to enable network discovery, likely to spread to other systems\\n- This indicates an active ransomware infection that could quickly spread across the network",\n "entitySummaryMarkdown": "{{ host.name SRVWIN02 }} infected with Sodinokibi ransomware executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion",\n "Impact"\n ],\n "summaryMarkdown": "Sodinokibi (REvil) ransomware detected on {{ host.name SRVWIN02 }}, actively encrypting files and attempting to spread",\n "title": "Active Sodinokibi Ransomware Infection"\n },\n {\n "alertIds": [\n "6f8e71d59956c6dbed5c88986cdafd4386684e3879085b2742e1f2d38b282066",\n "c13b78fbfef05ddc81c73b436ccb5288d8cd52a46175638b1b3b0d311f8b53e8",\n "b0f3d3f5bfc0b1d1f3c7e219ee44dc225fa26cafd40697073a636b44cf6054ad"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:22.077Z }}, suspicious activity was detected on {{ host.name SRVWIN06 }}\\n- The msiexec.exe process spawned an unusual PowerShell child process\\n- The PowerShell process executed a script from a suspicious temporary directory\\n- Memory scans of the PowerShell process detected signatures matching the Bumblebee malware loader\\n- Bumblebee is known to be used by multiple ransomware groups as an initial access vector\\n- This indicates a likely ongoing attack attempting to deploy additional malware or ransomware",\n "entitySummaryMarkdown": "{{ host.name SRVWIN06 }} infected with Bumblebee malware loader via {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Bumblebee malware loader detected on {{ host.name SRVWIN06 }}, likely attempting to deploy additional payloads",\n "title": "Bumblebee Malware Loader Detected"\n },\n {\n "alertIds": [\n "f629babc51c3628517d8a7e1f0662124ee41e4328b1dbcf72dc3fc6f2e410d33",\n "627d00600f803366edb83700b546a4bf486e2990ac7140d842e898eb6e298e83",\n "6181847506974ed4458f03b60919c4a306197b5cb040ab324d2d1f6d0ca5bde1",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "df26b2d23068b77fdc001ea44f46505a259f02ceccc9fa0b2401c5e35190e710",\n "9c038ff779bd0ff514a1ff2b55caa359189d8bcebc48c6ac14a789946e87eaed"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:27.839Z }}, a malicious Word document was opened on {{ host.name SRVWIN07 }}\\n- The document spawned wscript.exe to execute a malicious VBS script\\n- The VBS script then launched a PowerShell process with suspicious arguments\\n- PowerShell was used to create a scheduled task for persistence\\n- This attack chain indicates a likely attempt to establish a foothold for further malicious activities",\n "entitySummaryMarkdown": "{{ host.name SRVWIN07 }} compromised via malicious Word document opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Malicious Word document on {{ host.name SRVWIN07 }} led to execution of VBS and PowerShell scripts, establishing persistence via scheduled task",\n "title": "Malicious Document Leads to Persistence"\n }\n ]\n}'; - - expect(extractJson(input)).toBe(expected); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts deleted file mode 100644 index 79d3f9c0d0599..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const extractJson = (input: string): string => { - const regex = /```json\s*([\s\S]*?)(?:\s*```|$)/; - const match = input.match(regex); - - if (match && match[1]) { - return match[1].trim(); - } - - return input; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx deleted file mode 100644 index 7d6db4dd72dfd..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { generationsAreRepeating } from '.'; - -describe('getIsGenerationRepeating', () => { - it('returns true when all previous generations are the same as the current generation', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: ['gen1', 'gen1', 'gen1'], // <-- all the same, length 3 - sampleLastNGenerations: 3, - }); - - expect(result).toBe(true); - }); - - it('returns false when some of the previous generations are NOT the same as the current generation', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: ['gen1', 'gen2', 'gen1'], // <-- some are different, length 3 - sampleLastNGenerations: 3, - }); - - expect(result).toBe(false); - }); - - it('returns true when all *sampled* generations are the same as the current generation, and there are older samples past the last N', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: [ - 'gen2', // <-- older sample will be ignored - 'gen1', - 'gen1', - 'gen1', - ], - sampleLastNGenerations: 3, - }); - - expect(result).toBe(true); - }); - - it('returns false when some of the *sampled* generations are NOT the same as the current generation, and there are additional samples past the last N', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: [ - 'gen1', // <-- older sample will be ignored - 'gen1', - 'gen1', - 'gen2', - ], - sampleLastNGenerations: 3, - }); - - expect(result).toBe(false); - }); - - it('returns false when sampling fewer generations than sampleLastNGenerations, and all are the same as the current generation', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: ['gen1', 'gen1'], // <-- same, but only 2 generations - sampleLastNGenerations: 3, - }); - - expect(result).toBe(false); - }); - - it('returns false when sampling fewer generations than sampleLastNGenerations, and some are different from the current generation', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: ['gen1', 'gen2'], // <-- different, but only 2 generations - sampleLastNGenerations: 3, - }); - - expect(result).toBe(false); - }); - - it('returns false when there are no previous generations to sample', () => { - const result = generationsAreRepeating({ - currentGeneration: 'gen1', - previousGenerations: [], - sampleLastNGenerations: 3, - }); - - expect(result).toBe(false); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx deleted file mode 100644 index 6cc9cd86c9d2f..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** Returns true if the last n generations are repeating the same output */ -export const generationsAreRepeating = ({ - currentGeneration, - previousGenerations, - sampleLastNGenerations, -}: { - currentGeneration: string; - previousGenerations: string[]; - sampleLastNGenerations: number; -}): boolean => { - const generationsToSample = previousGenerations.slice(-sampleLastNGenerations); - - if (generationsToSample.length < sampleLastNGenerations) { - return false; // Not enough generations to sample - } - - return generationsToSample.every((generation) => generation === currentGeneration); -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts deleted file mode 100644 index 7eacaad1d7e39..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import { ChatPromptTemplate } from '@langchain/core/prompts'; -import { Runnable } from '@langchain/core/runnables'; - -import { getOutputParser } from '../get_output_parser'; - -interface GetChainWithFormatInstructions { - chain: Runnable; - formatInstructions: string; - llmType: string; -} - -export const getChainWithFormatInstructions = ( - llm: ActionsClientLlm -): GetChainWithFormatInstructions => { - const outputParser = getOutputParser(); - const formatInstructions = outputParser.getFormatInstructions(); - - const prompt = ChatPromptTemplate.fromTemplate( - `Answer the user's question as best you can:\n{format_instructions}\n{query}` - ); - - const chain = prompt.pipe(llm); - const llmType = llm._llmType(); - - return { chain, formatInstructions, llmType }; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts deleted file mode 100644 index 10b5c323891a1..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getCombined = ({ - combinedGenerations, - partialResponse, -}: { - combinedGenerations: string; - partialResponse: string; -}): string => `${combinedGenerations}${partialResponse}`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts deleted file mode 100644 index 4c9ac71f8310c..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty } from 'lodash/fp'; - -import { getAlertsContextPrompt } from '../../generate/helpers/get_alerts_context_prompt'; -import { getContinuePrompt } from '../get_continue_prompt'; - -/** - * Returns the the initial query, or the initial query combined with a - * continuation prompt and partial results - */ -export const getCombinedAttackDiscoveryPrompt = ({ - anonymizedAlerts, - attackDiscoveryPrompt, - combinedMaybePartialResults, -}: { - anonymizedAlerts: string[]; - attackDiscoveryPrompt: string; - /** combined results that may contain incomplete JSON */ - combinedMaybePartialResults: string; -}): string => { - const alertsContextPrompt = getAlertsContextPrompt({ - anonymizedAlerts, - attackDiscoveryPrompt, - }); - - return isEmpty(combinedMaybePartialResults) - ? alertsContextPrompt // no partial results yet - : `${alertsContextPrompt} - -${getContinuePrompt()} - -""" -${combinedMaybePartialResults} -""" - -`; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts deleted file mode 100644 index 628ba0531332c..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getContinuePrompt = - (): string => `Continue exactly where you left off in the JSON output below, generating only the additional JSON output when it's required to complete your work. The additional JSON output MUST ALWAYS follow these rules: -1) it MUST conform to the schema above, because it will be checked against the JSON schema -2) it MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds), because it will be parsed as JSON -3) it MUST NOT repeat any the previous output, because that would prevent partial results from being combined -4) it MUST NOT restart from the beginning, because that would prevent partial results from being combined -5) it MUST NOT be prefixed or suffixed with additional text outside of the JSON, because that would prevent it from being combined and parsed as JSON: -`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts deleted file mode 100644 index 25bace13d40c8..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getDefaultAttackDiscoveryPrompt = (): string => - "You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. You MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds)."; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts deleted file mode 100644 index 569c8cf4e04a5..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { getOutputParser } from '.'; - -describe('getOutputParser', () => { - it('returns a structured output parser with the expected format instructions', () => { - const outputParser = getOutputParser(); - - const expected = `You must format your output as a JSON value that adheres to a given \"JSON Schema\" instance. - -\"JSON Schema\" is a declarative language that allows you to annotate and validate JSON documents. - -For example, the example \"JSON Schema\" instance {{\"properties\": {{\"foo\": {{\"description\": \"a list of test words\", \"type\": \"array\", \"items\": {{\"type\": \"string\"}}}}}}, \"required\": [\"foo\"]}}}} -would match an object with one required property, \"foo\". The \"type\" property specifies \"foo\" must be an \"array\", and the \"description\" property semantically describes it as \"a list of test words\". The items within \"foo\" must be strings. -Thus, the object {{\"foo\": [\"bar\", \"baz\"]}} is a well-formatted instance of this example \"JSON Schema\". The object {{\"properties\": {{\"foo\": [\"bar\", \"baz\"]}}}} is not well-formatted. - -Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas! - -Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock: -\`\`\`json -{"type":"object","properties":{"insights":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"alertIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The alert IDs that the insight is based on.\"},\"detailsMarkdown\":{\"type\":\"string\",\"description\":\"A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"},\"entitySummaryMarkdown\":{\"type\":\"string\",\"description\":\"A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"mitreAttackTactics\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"An array of MITRE ATT&CK tactic for the insight, using one of the following values: Reconnaissance,Initial Access,Execution,Persistence,Privilege Escalation,Discovery,Lateral Movement,Command and Control,Exfiltration\"},\"summaryMarkdown\":{\"type\":\"string\",\"description\":\"A markdown summary of insight, using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"title\":{\"type\":\"string\",\"description\":\"A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.\"}},\"required\":[\"alertIds\",\"detailsMarkdown\",\"summaryMarkdown\",\"title\"],\"additionalProperties\":false},\"description\":\"Insights with markdown that always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"}},\"required\":[\"insights\"],\"additionalProperties":false,\"$schema\":\"http://json-schema.org/draft-07/schema#\"} -\`\`\` -`; - - expect(outputParser.getFormatInstructions()).toEqual(expected); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts deleted file mode 100644 index 2ca0d72b63eb4..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { StructuredOutputParser } from 'langchain/output_parsers'; - -import { AttackDiscoveriesGenerationSchema } from '../../generate/schema'; - -export const getOutputParser = () => - StructuredOutputParser.fromZodSchema(AttackDiscoveriesGenerationSchema); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts deleted file mode 100644 index 3f7a0a9d802b3..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Logger } from '@kbn/core/server'; -import type { AttackDiscovery } from '@kbn/elastic-assistant-common'; - -import { addTrailingBackticksIfNecessary } from '../add_trailing_backticks_if_necessary'; -import { extractJson } from '../extract_json'; -import { AttackDiscoveriesGenerationSchema } from '../../generate/schema'; - -export const parseCombinedOrThrow = ({ - combinedResponse, - generationAttempts, - llmType, - logger, - nodeName, -}: { - /** combined responses that maybe valid JSON */ - combinedResponse: string; - generationAttempts: number; - nodeName: string; - llmType: string; - logger?: Logger; -}): AttackDiscovery[] => { - const timestamp = new Date().toISOString(); - - const extractedJson = extractJson(addTrailingBackticksIfNecessary(combinedResponse)); - - logger?.debug( - () => - `${nodeName} node is parsing extractedJson (${llmType}) from attempt ${generationAttempts}` - ); - - const unvalidatedParsed = JSON.parse(extractedJson); - - logger?.debug( - () => - `${nodeName} node is validating combined response (${llmType}) from attempt ${generationAttempts}` - ); - - const validatedResponse = AttackDiscoveriesGenerationSchema.parse(unvalidatedParsed); - - logger?.debug( - () => - `${nodeName} node successfully validated Attack discoveries response (${llmType}) from attempt ${generationAttempts}` - ); - - return [...validatedResponse.insights.map((insight) => ({ ...insight, timestamp }))]; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts deleted file mode 100644 index f938f6436db98..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const responseIsHallucinated = (result: string): boolean => - result.includes('{{ host.name hostNameValue }}'); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts deleted file mode 100644 index e642e598e73f0..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { GraphState } from '../../../../types'; - -export const discardPreviousRefinements = ({ - generationAttempts, - hallucinationFailures, - isHallucinationDetected, - state, -}: { - generationAttempts: number; - hallucinationFailures: number; - isHallucinationDetected: boolean; - state: GraphState; -}): GraphState => { - return { - ...state, - combinedRefinements: '', // <-- reset the combined refinements - generationAttempts: generationAttempts + 1, - refinements: [], // <-- reset the refinements - hallucinationFailures: isHallucinationDetected - ? hallucinationFailures + 1 - : hallucinationFailures, - }; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts deleted file mode 100644 index 11ea40a48ae55..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { AttackDiscovery } from '@kbn/elastic-assistant-common'; -import { isEmpty } from 'lodash/fp'; - -import { getContinuePrompt } from '../../../helpers/get_continue_prompt'; - -/** - * Returns a prompt that combines the initial query, a refine prompt, and partial results - */ -export const getCombinedRefinePrompt = ({ - attackDiscoveryPrompt, - combinedRefinements, - refinePrompt, - unrefinedResults, -}: { - attackDiscoveryPrompt: string; - combinedRefinements: string; - refinePrompt: string; - unrefinedResults: AttackDiscovery[] | null; -}): string => { - const baseQuery = `${attackDiscoveryPrompt} - -${refinePrompt} - -""" -${JSON.stringify(unrefinedResults, null, 2)} -""" - -`; - - return isEmpty(combinedRefinements) - ? baseQuery // no partial results yet - : `${baseQuery} - -${getContinuePrompt()} - -""" -${combinedRefinements} -""" - -`; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts deleted file mode 100644 index 5743316669785..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getDefaultRefinePrompt = - (): string => `You previously generated the following insights, but sometimes they represent the same attack. - -Combine the insights below, when they represent the same attack; leave any insights that are not combined unchanged:`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts deleted file mode 100644 index 13d0a2228a3ee..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** - * Note: the conditions tested here are different than the generate node - */ -export const getUseUnrefinedResults = ({ - maxHallucinationFailuresReached, - maxRetriesReached, -}: { - maxHallucinationFailuresReached: boolean; - maxRetriesReached: boolean; -}): boolean => maxRetriesReached || maxHallucinationFailuresReached; // we may have reached max halucination failures, but we still want to use the unrefined results diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts deleted file mode 100644 index 0c7987eef92bc..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import type { Logger } from '@kbn/core/server'; - -import { discardPreviousRefinements } from './helpers/discard_previous_refinements'; -import { extractJson } from '../helpers/extract_json'; -import { getChainWithFormatInstructions } from '../helpers/get_chain_with_format_instructions'; -import { getCombined } from '../helpers/get_combined'; -import { getCombinedRefinePrompt } from './helpers/get_combined_refine_prompt'; -import { generationsAreRepeating } from '../helpers/generations_are_repeating'; -import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; -import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; -import { getUseUnrefinedResults } from './helpers/get_use_unrefined_results'; -import { parseCombinedOrThrow } from '../helpers/parse_combined_or_throw'; -import { responseIsHallucinated } from '../helpers/response_is_hallucinated'; -import type { GraphState } from '../../types'; - -export const getRefineNode = ({ - llm, - logger, -}: { - llm: ActionsClientLlm; - logger?: Logger; -}): ((state: GraphState) => Promise<GraphState>) => { - const refine = async (state: GraphState): Promise<GraphState> => { - logger?.debug(() => '---REFINE---'); - - const { - attackDiscoveryPrompt, - combinedRefinements, - generationAttempts, - hallucinationFailures, - maxGenerationAttempts, - maxHallucinationFailures, - maxRepeatedGenerations, - refinements, - refinePrompt, - unrefinedResults, - } = state; - - let combinedResponse = ''; // mutable, because it must be accessed in the catch block - let partialResponse = ''; // mutable, because it must be accessed in the catch block - - try { - const query = getCombinedRefinePrompt({ - attackDiscoveryPrompt, - combinedRefinements, - refinePrompt, - unrefinedResults, - }); - - const { chain, formatInstructions, llmType } = getChainWithFormatInstructions(llm); - - logger?.debug( - () => `refine node is invoking the chain (${llmType}), attempt ${generationAttempts}` - ); - - const rawResponse = (await chain.invoke({ - format_instructions: formatInstructions, - query, - })) as unknown as string; - - // LOCAL MUTATION: - partialResponse = extractJson(rawResponse); // remove the surrounding ```json``` - - // if the response is hallucinated, discard it: - if (responseIsHallucinated(partialResponse)) { - logger?.debug( - () => - `refine node detected a hallucination (${llmType}), on attempt ${generationAttempts}; discarding the accumulated refinements and starting over` - ); - - return discardPreviousRefinements({ - generationAttempts, - hallucinationFailures, - isHallucinationDetected: true, - state, - }); - } - - // if the refinements are repeating, discard previous refinements and start over: - if ( - generationsAreRepeating({ - currentGeneration: partialResponse, - previousGenerations: refinements, - sampleLastNGenerations: maxRepeatedGenerations, - }) - ) { - logger?.debug( - () => - `refine node detected (${llmType}), detected ${maxRepeatedGenerations} repeated generations on attempt ${generationAttempts}; discarding the accumulated results and starting over` - ); - - // discard the accumulated results and start over: - return discardPreviousRefinements({ - generationAttempts, - hallucinationFailures, - isHallucinationDetected: false, - state, - }); - } - - // LOCAL MUTATION: - combinedResponse = getCombined({ combinedGenerations: combinedRefinements, partialResponse }); // combine the new response with the previous ones - - const attackDiscoveries = parseCombinedOrThrow({ - combinedResponse, - generationAttempts, - llmType, - logger, - nodeName: 'refine', - }); - - return { - ...state, - attackDiscoveries, // the final, refined answer - generationAttempts: generationAttempts + 1, - combinedRefinements: combinedResponse, - refinements: [...refinements, partialResponse], - }; - } catch (error) { - const parsingError = `refine node is unable to parse (${llm._llmType()}) response from attempt ${generationAttempts}; (this may be an incomplete response from the model): ${error}`; - logger?.debug(() => parsingError); // logged at debug level because the error is expected when the model returns an incomplete response - - const maxRetriesReached = getMaxRetriesReached({ - generationAttempts: generationAttempts + 1, - maxGenerationAttempts, - }); - - const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ - hallucinationFailures, - maxHallucinationFailures, - }); - - // we will use the unrefined results if we have reached the maximum number of retries or hallucination failures: - const useUnrefinedResults = getUseUnrefinedResults({ - maxHallucinationFailuresReached, - maxRetriesReached, - }); - - if (useUnrefinedResults) { - logger?.debug( - () => - `refine node is using unrefined results response (${llm._llmType()}) from attempt ${generationAttempts}, because all attempts have been used` - ); - } - - return { - ...state, - attackDiscoveries: useUnrefinedResults ? unrefinedResults : null, - combinedRefinements: combinedResponse, - errors: [...state.errors, parsingError], - generationAttempts: generationAttempts + 1, - refinements: [...refinements, partialResponse], - }; - } - }; - - return refine; -}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts deleted file mode 100644 index 3a8b7ed3a6b94..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core/server'; -import { Replacements } from '@kbn/elastic-assistant-common'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -import type { CallbackManagerForRetrieverRun } from '@langchain/core/callbacks/manager'; -import type { Document } from '@langchain/core/documents'; -import { BaseRetriever, type BaseRetrieverInput } from '@langchain/core/retrievers'; - -import { getAnonymizedAlerts } from '../helpers/get_anonymized_alerts'; - -export type CustomRetrieverInput = BaseRetrieverInput; - -export class AnonymizedAlertsRetriever extends BaseRetriever { - lc_namespace = ['langchain', 'retrievers']; - - #alertsIndexPattern?: string; - #anonymizationFields?: AnonymizationFieldResponse[]; - #esClient: ElasticsearchClient; - #onNewReplacements?: (newReplacements: Replacements) => void; - #replacements?: Replacements; - #size?: number; - - constructor({ - alertsIndexPattern, - anonymizationFields, - fields, - esClient, - onNewReplacements, - replacements, - size, - }: { - alertsIndexPattern?: string; - anonymizationFields?: AnonymizationFieldResponse[]; - fields?: CustomRetrieverInput; - esClient: ElasticsearchClient; - onNewReplacements?: (newReplacements: Replacements) => void; - replacements?: Replacements; - size?: number; - }) { - super(fields); - - this.#alertsIndexPattern = alertsIndexPattern; - this.#anonymizationFields = anonymizationFields; - this.#esClient = esClient; - this.#onNewReplacements = onNewReplacements; - this.#replacements = replacements; - this.#size = size; - } - - async _getRelevantDocuments( - query: string, - runManager?: CallbackManagerForRetrieverRun - ): Promise<Document[]> { - const anonymizedAlerts = await getAnonymizedAlerts({ - alertsIndexPattern: this.#alertsIndexPattern, - anonymizationFields: this.#anonymizationFields, - esClient: this.#esClient, - onNewReplacements: this.#onNewReplacements, - replacements: this.#replacements, - size: this.#size, - }); - - return anonymizedAlerts.map((alert) => ({ - pageContent: alert, - metadata: {}, - })); - } -} diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts deleted file mode 100644 index 951ae3bca8854..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { Replacements } from '@kbn/elastic-assistant-common'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; - -import { AnonymizedAlertsRetriever } from './anonymized_alerts_retriever'; -import type { GraphState } from '../../types'; - -export const getRetrieveAnonymizedAlertsNode = ({ - alertsIndexPattern, - anonymizationFields, - esClient, - logger, - onNewReplacements, - replacements, - size, -}: { - alertsIndexPattern?: string; - anonymizationFields?: AnonymizationFieldResponse[]; - esClient: ElasticsearchClient; - logger?: Logger; - onNewReplacements?: (replacements: Replacements) => void; - replacements?: Replacements; - size?: number; -}): ((state: GraphState) => Promise<GraphState>) => { - let localReplacements = { ...(replacements ?? {}) }; - const localOnNewReplacements = (newReplacements: Replacements) => { - localReplacements = { ...localReplacements, ...newReplacements }; - - onNewReplacements?.(localReplacements); // invoke the callback with the latest replacements - }; - - const retriever = new AnonymizedAlertsRetriever({ - alertsIndexPattern, - anonymizationFields, - esClient, - onNewReplacements: localOnNewReplacements, - replacements, - size, - }); - - const retrieveAnonymizedAlerts = async (state: GraphState): Promise<GraphState> => { - logger?.debug(() => '---RETRIEVE ANONYMIZED ALERTS---'); - const documents = await retriever - .withConfig({ runName: 'runAnonymizedAlertsRetriever' }) - .invoke(''); - - return { - ...state, - anonymizedAlerts: documents, - replacements: localReplacements, - }; - }; - - return retrieveAnonymizedAlerts; -}; - -/** - * Retrieve documents - * - * @param {GraphState} state The current state of the graph. - * @param {RunnableConfig | undefined} config The configuration object for tracing. - * @returns {Promise<GraphState>} The new state object. - */ diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts deleted file mode 100644 index 4229155cc2e25..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; -import type { Document } from '@langchain/core/documents'; -import type { StateGraphArgs } from '@langchain/langgraph'; - -import { - DEFAULT_MAX_GENERATION_ATTEMPTS, - DEFAULT_MAX_HALLUCINATION_FAILURES, - DEFAULT_MAX_REPEATED_GENERATIONS, -} from '../constants'; -import { getDefaultAttackDiscoveryPrompt } from '../nodes/helpers/get_default_attack_discovery_prompt'; -import { getDefaultRefinePrompt } from '../nodes/refine/helpers/get_default_refine_prompt'; -import type { GraphState } from '../types'; - -export const getDefaultGraphState = (): StateGraphArgs<GraphState>['channels'] => ({ - attackDiscoveries: { - value: (x: AttackDiscovery[] | null, y?: AttackDiscovery[] | null) => y ?? x, - default: () => null, - }, - attackDiscoveryPrompt: { - value: (x: string, y?: string) => y ?? x, - default: () => getDefaultAttackDiscoveryPrompt(), - }, - anonymizedAlerts: { - value: (x: Document[], y?: Document[]) => y ?? x, - default: () => [], - }, - combinedGenerations: { - value: (x: string, y?: string) => y ?? x, - default: () => '', - }, - combinedRefinements: { - value: (x: string, y?: string) => y ?? x, - default: () => '', - }, - errors: { - value: (x: string[], y?: string[]) => y ?? x, - default: () => [], - }, - generationAttempts: { - value: (x: number, y?: number) => y ?? x, - default: () => 0, - }, - generations: { - value: (x: string[], y?: string[]) => y ?? x, - default: () => [], - }, - hallucinationFailures: { - value: (x: number, y?: number) => y ?? x, - default: () => 0, - }, - refinePrompt: { - value: (x: string, y?: string) => y ?? x, - default: () => getDefaultRefinePrompt(), - }, - maxGenerationAttempts: { - value: (x: number, y?: number) => y ?? x, - default: () => DEFAULT_MAX_GENERATION_ATTEMPTS, - }, - maxHallucinationFailures: { - value: (x: number, y?: number) => y ?? x, - default: () => DEFAULT_MAX_HALLUCINATION_FAILURES, - }, - maxRepeatedGenerations: { - value: (x: number, y?: number) => y ?? x, - default: () => DEFAULT_MAX_REPEATED_GENERATIONS, - }, - refinements: { - value: (x: string[], y?: string[]) => y ?? x, - default: () => [], - }, - replacements: { - value: (x: Replacements, y?: Replacements) => y ?? x, - default: () => ({}), - }, - unrefinedResults: { - value: (x: AttackDiscovery[] | null, y?: AttackDiscovery[] | null) => y ?? x, - default: () => null, - }, -}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts deleted file mode 100644 index b4473a02b82ae..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; -import type { Document } from '@langchain/core/documents'; - -export interface GraphState { - attackDiscoveries: AttackDiscovery[] | null; - attackDiscoveryPrompt: string; - anonymizedAlerts: Document[]; - combinedGenerations: string; - combinedRefinements: string; - errors: string[]; - generationAttempts: number; - generations: string[]; - hallucinationFailures: number; - maxGenerationAttempts: number; - maxHallucinationFailures: number; - maxRepeatedGenerations: number; - refinements: string[]; - refinePrompt: string; - replacements: Replacements; - unrefinedResults: AttackDiscovery[] | null; -} diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts index b9e4f85a800a0..706da7197f31a 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts @@ -10,41 +10,14 @@ import { GetDefaultAssistantGraphParams, DefaultAssistantGraph, } from './default_assistant_graph/graph'; -import { - DefaultAttackDiscoveryGraph, - GetDefaultAttackDiscoveryGraphParams, - getDefaultAttackDiscoveryGraph, -} from '../../attack_discovery/graphs/default_attack_discovery_graph'; export type GetAssistantGraph = (params: GetDefaultAssistantGraphParams) => DefaultAssistantGraph; -export type GetAttackDiscoveryGraph = ( - params: GetDefaultAttackDiscoveryGraphParams -) => DefaultAttackDiscoveryGraph; - -export type GraphType = 'assistant' | 'attack-discovery'; - -export interface AssistantGraphMetadata { - getDefaultAssistantGraph: GetAssistantGraph; - graphType: 'assistant'; -} - -export interface AttackDiscoveryGraphMetadata { - getDefaultAttackDiscoveryGraph: GetAttackDiscoveryGraph; - graphType: 'attack-discovery'; -} - -export type GraphMetadata = AssistantGraphMetadata | AttackDiscoveryGraphMetadata; /** * Map of the different Assistant Graphs. Useful for running evaluations. */ -export const ASSISTANT_GRAPH_MAP: Record<string, GraphMetadata> = { - DefaultAssistantGraph: { - getDefaultAssistantGraph, - graphType: 'assistant', - }, - DefaultAttackDiscoveryGraph: { - getDefaultAttackDiscoveryGraph, - graphType: 'attack-discovery', - }, +export const ASSISTANT_GRAPH_MAP: Record<string, GetAssistantGraph> = { + DefaultAssistantGraph: getDefaultAssistantGraph, + // TODO: Support additional graphs + // AttackDiscoveryGraph: getDefaultAssistantGraph, }; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts similarity index 80% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts index 9f5efbe5041d5..66aca77f1eb8b 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts @@ -8,23 +8,15 @@ import { cancelAttackDiscoveryRoute } from './cancel_attack_discovery'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { serverMock } from '../../../../__mocks__/server'; -import { requestContextMock } from '../../../../__mocks__/request_context'; +import { serverMock } from '../../__mocks__/server'; +import { requestContextMock } from '../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { AttackDiscoveryDataClient } from '../../../../lib/attack_discovery/persistence'; -import { transformESSearchToAttackDiscovery } from '../../../../lib/attack_discovery/persistence/transforms/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; -import { getCancelAttackDiscoveryRequest } from '../../../../__mocks__/request'; -import { updateAttackDiscoveryStatusToCanceled } from '../../helpers/helpers'; - -jest.mock('../../helpers/helpers', () => { - const original = jest.requireActual('../../helpers/helpers'); - - return { - ...original, - updateAttackDiscoveryStatusToCanceled: jest.fn(), - }; -}); +import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; +import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { getCancelAttackDiscoveryRequest } from '../../__mocks__/request'; +import { updateAttackDiscoveryStatusToCanceled } from './helpers'; +jest.mock('./helpers'); const { clients, context } = requestContextMock.createTools(); const server: ReturnType<typeof serverMock.create> = serverMock.create(); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts similarity index 91% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts index 86631708b1cf7..47b748c9c432a 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts @@ -14,16 +14,16 @@ import { } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateAttackDiscoveryStatusToCanceled } from '../../helpers/helpers'; -import { ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID } from '../../../../../common/constants'; -import { buildResponse } from '../../../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../../../types'; +import { updateAttackDiscoveryStatusToCanceled } from './helpers'; +import { ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID } from '../../../common/constants'; +import { buildResponse } from '../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../types'; export const cancelAttackDiscoveryRoute = ( router: IRouter<ElasticAssistantRequestHandlerContext> ) => { router.versioned - .post({ + .put({ access: 'internal', path: ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID, options: { diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts similarity index 85% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts index ce07d66b9606e..74cf160c43ffe 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts @@ -8,24 +8,15 @@ import { getAttackDiscoveryRoute } from './get_attack_discovery'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { serverMock } from '../../../__mocks__/server'; -import { requestContextMock } from '../../../__mocks__/request_context'; +import { serverMock } from '../../__mocks__/server'; +import { requestContextMock } from '../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; -import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; -import { getAttackDiscoveryRequest } from '../../../__mocks__/request'; -import { getAttackDiscoveryStats, updateAttackDiscoveryLastViewedAt } from '../helpers/helpers'; - -jest.mock('../helpers/helpers', () => { - const original = jest.requireActual('../helpers/helpers'); - - return { - ...original, - getAttackDiscoveryStats: jest.fn(), - updateAttackDiscoveryLastViewedAt: jest.fn(), - }; -}); +import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; +import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoveryRequest } from '../../__mocks__/request'; +import { getAttackDiscoveryStats, updateAttackDiscoveryLastViewedAt } from './helpers'; +jest.mock('./helpers'); const mockStats = { newConnectorResultsCount: 2, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts index e3756b10a3fb3..09b2df98fe090 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts @@ -14,10 +14,10 @@ import { } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateAttackDiscoveryLastViewedAt, getAttackDiscoveryStats } from '../helpers/helpers'; -import { ATTACK_DISCOVERY_BY_CONNECTOR_ID } from '../../../../common/constants'; -import { buildResponse } from '../../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../../types'; +import { updateAttackDiscoveryLastViewedAt, getAttackDiscoveryStats } from './helpers'; +import { ATTACK_DISCOVERY_BY_CONNECTOR_ID } from '../../../common/constants'; +import { buildResponse } from '../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../types'; export const getAttackDiscoveryRoute = (router: IRouter<ElasticAssistantRequestHandlerContext>) => { router.versioned diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts new file mode 100644 index 0000000000000..d5eaf7d159618 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts @@ -0,0 +1,805 @@ +/* + * 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 { AuthenticatedUser } from '@kbn/core-security-common'; +import moment from 'moment'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; + +import { + REQUIRED_FOR_ATTACK_DISCOVERY, + addGenerationInterval, + attackDiscoveryStatus, + getAssistantToolParams, + handleToolError, + updateAttackDiscoveryStatusToCanceled, + updateAttackDiscoveryStatusToRunning, + updateAttackDiscoveries, + getAttackDiscoveryStats, +} from './helpers'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; +import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { loggerMock } from '@kbn/logging-mocks'; +import { KibanaRequest } from '@kbn/core-http-server'; +import { + AttackDiscoveryPostRequestBody, + ExecuteConnectorRequestBody, +} from '@kbn/elastic-assistant-common'; +import { coreMock } from '@kbn/core/server/mocks'; +import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; + +import { + getAnonymizationFieldMock, + getUpdateAnonymizationFieldSchemaMock, +} from '../../__mocks__/anonymization_fields_schema.mock'; + +jest.mock('lodash/fp', () => ({ + uniq: jest.fn((arr) => Array.from(new Set(arr))), +})); + +jest.mock('@kbn/securitysolution-es-utils', () => ({ + transformError: jest.fn((err) => err), +})); +jest.mock('@kbn/langchain/server', () => ({ + ActionsClientLlm: jest.fn(), +})); +jest.mock('../evaluate/utils', () => ({ + getLangSmithTracer: jest.fn().mockReturnValue([]), +})); +jest.mock('../utils', () => ({ + getLlmType: jest.fn().mockReturnValue('llm-type'), +})); +const findAttackDiscoveryByConnectorId = jest.fn(); +const updateAttackDiscovery = jest.fn(); +const createAttackDiscovery = jest.fn(); +const getAttackDiscovery = jest.fn(); +const findAllAttackDiscoveries = jest.fn(); +const mockDataClient = { + findAttackDiscoveryByConnectorId, + updateAttackDiscovery, + createAttackDiscovery, + getAttackDiscovery, + findAllAttackDiscoveries, +} as unknown as AttackDiscoveryDataClient; +const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); +const mockLogger = loggerMock.create(); +const mockTelemetry = coreMock.createSetup().analytics; +const mockError = new Error('Test error'); + +const mockAuthenticatedUser = { + username: 'user', + profile_uid: '1234', + authentication_realm: { + type: 'my_realm_type', + name: 'my_realm_name', + }, +} as AuthenticatedUser; + +const mockApiConfig = { + connectorId: 'connector-id', + actionTypeId: '.bedrock', + model: 'model', + provider: OpenAiProviderType.OpenAi, +}; + +const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockRequest: KibanaRequest<unknown, unknown, any, any> = {} as unknown as KibanaRequest< + unknown, + unknown, + any, // eslint-disable-line @typescript-eslint/no-explicit-any + any // eslint-disable-line @typescript-eslint/no-explicit-any +>; + +describe('helpers', () => { + const date = '2024-03-28T22:27:28.000Z'; + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + beforeEach(() => { + jest.clearAllMocks(); + jest.setSystemTime(new Date(date)); + getAttackDiscovery.mockResolvedValue(mockCurrentAd); + updateAttackDiscovery.mockResolvedValue({}); + }); + describe('getAssistantToolParams', () => { + const alertsIndexPattern = '.alerts-security.alerts-default'; + const esClient = elasticsearchClientMock.createElasticsearchClient(); + const actionsClient = actionsClientMock.create(); + const langChainTimeout = 1000; + const latestReplacements = {}; + const llm = new ActionsClientLlm({ + actionsClient, + connectorId: 'test-connecter-id', + llmType: 'bedrock', + logger: mockLogger, + temperature: 0, + timeout: 580000, + }); + const onNewReplacements = jest.fn(); + const size = 20; + + const mockParams = { + actionsClient, + alertsIndexPattern: 'alerts-*', + anonymizationFields: [{ id: '1', field: 'field1', allowed: true, anonymized: true }], + apiConfig: mockApiConfig, + esClient: mockEsClient, + connectorTimeout: 1000, + langChainTimeout: 2000, + langSmithProject: 'project', + langSmithApiKey: 'api-key', + logger: mockLogger, + latestReplacements: {}, + onNewReplacements: jest.fn(), + request: {} as KibanaRequest< + unknown, + unknown, + ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody + >, + size: 10, + }; + + it('should return formatted assistant tool params', () => { + const result = getAssistantToolParams(mockParams); + + expect(ActionsClientLlm).toHaveBeenCalledWith( + expect.objectContaining({ + connectorId: 'connector-id', + llmType: 'llm-type', + }) + ); + expect(result.anonymizationFields).toEqual([ + ...mockParams.anonymizationFields, + ...REQUIRED_FOR_ATTACK_DISCOVERY, + ]); + }); + + it('returns the expected AssistantToolParams when anonymizationFields are provided', () => { + const anonymizationFields = [ + getAnonymizationFieldMock(getUpdateAnonymizationFieldSchemaMock()), + ]; + + const result = getAssistantToolParams({ + actionsClient, + alertsIndexPattern, + apiConfig: mockApiConfig, + anonymizationFields, + connectorTimeout: 1000, + latestReplacements, + esClient, + langChainTimeout, + logger: mockLogger, + onNewReplacements, + request: mockRequest, + size, + }); + + expect(result).toEqual({ + alertsIndexPattern, + anonymizationFields: [...anonymizationFields, ...REQUIRED_FOR_ATTACK_DISCOVERY], + isEnabledKnowledgeBase: false, + chain: undefined, + esClient, + langChainTimeout, + llm, + logger: mockLogger, + onNewReplacements, + replacements: latestReplacements, + request: mockRequest, + size, + }); + }); + + it('returns the expected AssistantToolParams when anonymizationFields is undefined', () => { + const anonymizationFields = undefined; + + const result = getAssistantToolParams({ + actionsClient, + alertsIndexPattern, + apiConfig: mockApiConfig, + anonymizationFields, + connectorTimeout: 1000, + latestReplacements, + esClient, + langChainTimeout, + logger: mockLogger, + onNewReplacements, + request: mockRequest, + size, + }); + + expect(result).toEqual({ + alertsIndexPattern, + anonymizationFields: [...REQUIRED_FOR_ATTACK_DISCOVERY], + isEnabledKnowledgeBase: false, + chain: undefined, + esClient, + langChainTimeout, + llm, + logger: mockLogger, + onNewReplacements, + replacements: latestReplacements, + request: mockRequest, + size, + }); + }); + + describe('addGenerationInterval', () => { + const generationInterval = { date: '2024-01-01T00:00:00Z', durationMs: 1000 }; + const existingIntervals = [ + { date: '2024-01-02T00:00:00Z', durationMs: 2000 }, + { date: '2024-01-03T00:00:00Z', durationMs: 3000 }, + ]; + + it('should add new interval and maintain length within MAX_GENERATION_INTERVALS', () => { + const result = addGenerationInterval(existingIntervals, generationInterval); + expect(result.length).toBeLessThanOrEqual(5); + expect(result).toContain(generationInterval); + }); + + it('should remove the oldest interval if exceeding MAX_GENERATION_INTERVALS', () => { + const longExistingIntervals = [...Array(5)].map((_, i) => ({ + date: `2024-01-0${i + 2}T00:00:00Z`, + durationMs: (i + 2) * 1000, + })); + const result = addGenerationInterval(longExistingIntervals, generationInterval); + expect(result.length).toBe(5); + expect(result).not.toContain(longExistingIntervals[4]); + }); + }); + + describe('updateAttackDiscoveryStatusToRunning', () => { + it('should update existing attack discovery to running', async () => { + const existingAd = { id: 'existing-id', backingIndex: 'index' }; + findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); + updateAttackDiscovery.mockResolvedValue(existingAd); + + const result = await updateAttackDiscoveryStatusToRunning( + mockDataClient, + mockAuthenticatedUser, + mockApiConfig + ); + + expect(findAttackDiscoveryByConnectorId).toHaveBeenCalledWith({ + connectorId: mockApiConfig.connectorId, + authenticatedUser: mockAuthenticatedUser, + }); + expect(updateAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryUpdateProps: expect.objectContaining({ + status: attackDiscoveryStatus.running, + }), + authenticatedUser: mockAuthenticatedUser, + }); + expect(result).toEqual({ attackDiscoveryId: existingAd.id, currentAd: existingAd }); + }); + + it('should create a new attack discovery if none exists', async () => { + const newAd = { id: 'new-id', backingIndex: 'index' }; + findAttackDiscoveryByConnectorId.mockResolvedValue(null); + createAttackDiscovery.mockResolvedValue(newAd); + + const result = await updateAttackDiscoveryStatusToRunning( + mockDataClient, + mockAuthenticatedUser, + mockApiConfig + ); + + expect(createAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryCreate: expect.objectContaining({ + status: attackDiscoveryStatus.running, + }), + authenticatedUser: mockAuthenticatedUser, + }); + expect(result).toEqual({ attackDiscoveryId: newAd.id, currentAd: newAd }); + }); + + it('should throw an error if updating or creating attack discovery fails', async () => { + findAttackDiscoveryByConnectorId.mockResolvedValue(null); + createAttackDiscovery.mockResolvedValue(null); + + await expect( + updateAttackDiscoveryStatusToRunning(mockDataClient, mockAuthenticatedUser, mockApiConfig) + ).rejects.toThrow('Could not create attack discovery for connectorId: connector-id'); + }); + }); + + describe('updateAttackDiscoveryStatusToCanceled', () => { + const existingAd = { + id: 'existing-id', + backingIndex: 'index', + status: attackDiscoveryStatus.running, + }; + it('should update existing attack discovery to canceled', async () => { + findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); + updateAttackDiscovery.mockResolvedValue(existingAd); + + const result = await updateAttackDiscoveryStatusToCanceled( + mockDataClient, + mockAuthenticatedUser, + mockApiConfig.connectorId + ); + + expect(findAttackDiscoveryByConnectorId).toHaveBeenCalledWith({ + connectorId: mockApiConfig.connectorId, + authenticatedUser: mockAuthenticatedUser, + }); + expect(updateAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryUpdateProps: expect.objectContaining({ + status: attackDiscoveryStatus.canceled, + }), + authenticatedUser: mockAuthenticatedUser, + }); + expect(result).toEqual(existingAd); + }); + + it('should throw an error if attack discovery is not running', async () => { + findAttackDiscoveryByConnectorId.mockResolvedValue({ + ...existingAd, + status: attackDiscoveryStatus.succeeded, + }); + await expect( + updateAttackDiscoveryStatusToCanceled( + mockDataClient, + mockAuthenticatedUser, + mockApiConfig.connectorId + ) + ).rejects.toThrow( + 'Connector id connector-id does not have a running attack discovery, and therefore cannot be canceled.' + ); + }); + + it('should throw an error if attack discovery does not exist', async () => { + findAttackDiscoveryByConnectorId.mockResolvedValue(null); + await expect( + updateAttackDiscoveryStatusToCanceled( + mockDataClient, + mockAuthenticatedUser, + mockApiConfig.connectorId + ) + ).rejects.toThrow('Could not find attack discovery for connector id: connector-id'); + }); + it('should throw error if updateAttackDiscovery returns null', async () => { + findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); + updateAttackDiscovery.mockResolvedValue(null); + + await expect( + updateAttackDiscoveryStatusToCanceled( + mockDataClient, + mockAuthenticatedUser, + mockApiConfig.connectorId + ) + ).rejects.toThrow('Could not update attack discovery for connector id: connector-id'); + }); + }); + + describe('updateAttackDiscoveries', () => { + const mockAttackDiscoveryId = 'attack-discovery-id'; + const mockLatestReplacements = {}; + const mockRawAttackDiscoveries = JSON.stringify({ + alertsContextCount: 5, + attackDiscoveries: [{ alertIds: ['alert-1', 'alert-2'] }, { alertIds: ['alert-3'] }], + }); + const mockSize = 10; + const mockStartTime = moment('2024-03-28T22:25:28.000Z'); + + const mockArgs = { + apiConfig: mockApiConfig, + attackDiscoveryId: mockAttackDiscoveryId, + authenticatedUser: mockAuthenticatedUser, + dataClient: mockDataClient, + latestReplacements: mockLatestReplacements, + logger: mockLogger, + rawAttackDiscoveries: mockRawAttackDiscoveries, + size: mockSize, + startTime: mockStartTime, + telemetry: mockTelemetry, + }; + + it('should update attack discoveries and report success telemetry', async () => { + await updateAttackDiscoveries(mockArgs); + + expect(updateAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryUpdateProps: { + alertsContextCount: 5, + attackDiscoveries: [{ alertIds: ['alert-1', 'alert-2'] }, { alertIds: ['alert-3'] }], + status: attackDiscoveryStatus.succeeded, + id: mockAttackDiscoveryId, + replacements: mockLatestReplacements, + backingIndex: mockCurrentAd.backingIndex, + generationIntervals: [ + { date, durationMs: 120000 }, + ...mockCurrentAd.generationIntervals, + ], + }, + authenticatedUser: mockAuthenticatedUser, + }); + + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_success', { + actionTypeId: mockApiConfig.actionTypeId, + alertsContextCount: 5, + alertsCount: 3, + configuredAlertsCount: mockSize, + discoveriesGenerated: 2, + durationMs: 120000, + model: mockApiConfig.model, + provider: mockApiConfig.provider, + }); + }); + + it('should update attack discoveries without generation interval if no discoveries are found', async () => { + const noDiscoveriesRaw = JSON.stringify({ + alertsContextCount: 0, + attackDiscoveries: [], + }); + + await updateAttackDiscoveries({ + ...mockArgs, + rawAttackDiscoveries: noDiscoveriesRaw, + }); + + expect(updateAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryUpdateProps: { + alertsContextCount: 0, + attackDiscoveries: [], + status: attackDiscoveryStatus.succeeded, + id: mockAttackDiscoveryId, + replacements: mockLatestReplacements, + backingIndex: mockCurrentAd.backingIndex, + }, + authenticatedUser: mockAuthenticatedUser, + }); + + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_success', { + actionTypeId: mockApiConfig.actionTypeId, + alertsContextCount: 0, + alertsCount: 0, + configuredAlertsCount: mockSize, + discoveriesGenerated: 0, + durationMs: 120000, + model: mockApiConfig.model, + provider: mockApiConfig.provider, + }); + }); + + it('should catch and log an error if raw attack discoveries is null', async () => { + await updateAttackDiscoveries({ + ...mockArgs, + rawAttackDiscoveries: null, + }); + expect(mockLogger.error).toHaveBeenCalledTimes(1); + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { + actionTypeId: mockArgs.apiConfig.actionTypeId, + errorMessage: 'tool returned no attack discoveries', + model: mockArgs.apiConfig.model, + provider: mockArgs.apiConfig.provider, + }); + }); + + it('should return and not call updateAttackDiscovery when getAttackDiscovery returns a canceled response', async () => { + getAttackDiscovery.mockResolvedValue({ + ...mockCurrentAd, + status: attackDiscoveryStatus.canceled, + }); + await updateAttackDiscoveries(mockArgs); + + expect(mockLogger.error).not.toHaveBeenCalled(); + expect(updateAttackDiscovery).not.toHaveBeenCalled(); + }); + + it('should log the error and report telemetry when getAttackDiscovery rejects', async () => { + getAttackDiscovery.mockRejectedValue(mockError); + await updateAttackDiscoveries(mockArgs); + + expect(mockLogger.error).toHaveBeenCalledWith(mockError); + expect(updateAttackDiscovery).not.toHaveBeenCalled(); + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { + actionTypeId: mockArgs.apiConfig.actionTypeId, + errorMessage: mockError.message, + model: mockArgs.apiConfig.model, + provider: mockArgs.apiConfig.provider, + }); + }); + }); + + describe('handleToolError', () => { + const mockArgs = { + apiConfig: mockApiConfig, + attackDiscoveryId: 'discovery-id', + authenticatedUser: mockAuthenticatedUser, + backingIndex: 'backing-index', + dataClient: mockDataClient, + err: mockError, + latestReplacements: {}, + logger: mockLogger, + telemetry: mockTelemetry, + }; + + it('should log the error and update attack discovery status to failed', async () => { + await handleToolError(mockArgs); + + expect(mockLogger.error).toHaveBeenCalledWith(mockError); + expect(updateAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryUpdateProps: { + status: attackDiscoveryStatus.failed, + attackDiscoveries: [], + backingIndex: 'foo', + failureReason: 'Test error', + id: 'discovery-id', + replacements: {}, + }, + authenticatedUser: mockArgs.authenticatedUser, + }); + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { + actionTypeId: mockArgs.apiConfig.actionTypeId, + errorMessage: mockError.message, + model: mockArgs.apiConfig.model, + provider: mockArgs.apiConfig.provider, + }); + }); + + it('should log the error and report telemetry when updateAttackDiscovery rejects', async () => { + updateAttackDiscovery.mockRejectedValue(mockError); + await handleToolError(mockArgs); + + expect(mockLogger.error).toHaveBeenCalledWith(mockError); + expect(updateAttackDiscovery).toHaveBeenCalledWith({ + attackDiscoveryUpdateProps: { + status: attackDiscoveryStatus.failed, + attackDiscoveries: [], + backingIndex: 'foo', + failureReason: 'Test error', + id: 'discovery-id', + replacements: {}, + }, + authenticatedUser: mockArgs.authenticatedUser, + }); + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { + actionTypeId: mockArgs.apiConfig.actionTypeId, + errorMessage: mockError.message, + model: mockArgs.apiConfig.model, + provider: mockArgs.apiConfig.provider, + }); + }); + + it('should return and not call updateAttackDiscovery when getAttackDiscovery returns a canceled response', async () => { + getAttackDiscovery.mockResolvedValue({ + ...mockCurrentAd, + status: attackDiscoveryStatus.canceled, + }); + await handleToolError(mockArgs); + + expect(mockTelemetry.reportEvent).not.toHaveBeenCalled(); + expect(updateAttackDiscovery).not.toHaveBeenCalled(); + }); + + it('should log the error and report telemetry when getAttackDiscovery rejects', async () => { + getAttackDiscovery.mockRejectedValue(mockError); + await handleToolError(mockArgs); + + expect(mockLogger.error).toHaveBeenCalledWith(mockError); + expect(updateAttackDiscovery).not.toHaveBeenCalled(); + expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { + actionTypeId: mockArgs.apiConfig.actionTypeId, + errorMessage: mockError.message, + model: mockArgs.apiConfig.model, + provider: mockArgs.apiConfig.provider, + }); + }); + }); + }); + describe('getAttackDiscoveryStats', () => { + const mockDiscoveries = [ + { + timestamp: '2024-06-13T17:55:11.360Z', + id: '8abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:55:11.360Z', + updatedAt: '2024-06-17T20:47:57.556Z', + lastViewedAt: '2024-06-17T20:47:57.556Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'failed', + alertsContextCount: undefined, + apiConfig: { + connectorId: 'my-bedrock-old', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: + 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', + }, + { + timestamp: '2024-06-13T17:55:11.360Z', + id: '9abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:55:11.360Z', + updatedAt: '2024-06-17T20:47:57.556Z', + lastViewedAt: '2024-06-17T20:46:57.556Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'failed', + alertsContextCount: undefined, + apiConfig: { + connectorId: 'my-bedrock-old', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: + 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', + }, + { + timestamp: '2024-06-12T19:54:50.428Z', + id: '745e005b-7248-4c08-b8b6-4cad263b4be0', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T19:54:50.428Z', + updatedAt: '2024-06-17T20:47:27.182Z', + lastViewedAt: '2024-06-17T20:27:27.182Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'running', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-gen-ai', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-13T17:50:59.409Z', + id: 'f48da2ca-b63e-4387-82d7-1423a68500aa', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:50:59.409Z', + updatedAt: '2024-06-17T20:47:59.969Z', + lastViewedAt: '2024-06-17T20:47:35.227Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'succeeded', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-gpt4o-ai', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-12T21:18:56.377Z', + id: '82fced1d-de48-42db-9f56-e45122dee017', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T21:18:56.377Z', + updatedAt: '2024-06-17T20:47:39.372Z', + lastViewedAt: '2024-06-17T20:47:39.372Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'canceled', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-bedrock', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-12T16:44:23.107Z', + id: 'a4709094-6116-484b-b096-1e8d151cb4b7', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T16:44:23.107Z', + updatedAt: '2024-06-17T20:48:16.961Z', + lastViewedAt: '2024-06-17T20:47:16.961Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'succeeded', + alertsContextCount: 0, + apiConfig: { + connectorId: 'my-gen-a2i', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [ + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: 'steph threw an error', + }, + ]; + beforeEach(() => { + findAllAttackDiscoveries.mockResolvedValue(mockDiscoveries); + }); + it('returns the formatted stats object', async () => { + const stats = await getAttackDiscoveryStats({ + authenticatedUser: mockAuthenticatedUser, + dataClient: mockDataClient, + }); + expect(stats).toEqual([ + { + hasViewed: true, + status: 'failed', + count: 0, + connectorId: 'my-bedrock-old', + }, + { + hasViewed: false, + status: 'failed', + count: 0, + connectorId: 'my-bedrock-old', + }, + { + hasViewed: false, + status: 'running', + count: 1, + connectorId: 'my-gen-ai', + }, + { + hasViewed: false, + status: 'succeeded', + count: 1, + connectorId: 'my-gpt4o-ai', + }, + { + hasViewed: true, + status: 'canceled', + count: 1, + connectorId: 'my-bedrock', + }, + { + hasViewed: false, + status: 'succeeded', + count: 4, + connectorId: 'my-gen-a2i', + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts similarity index 55% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts index 188976f0b3f5c..f016d6ac29118 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts @@ -5,29 +5,38 @@ * 2.0. */ -import { AnalyticsServiceSetup, AuthenticatedUser, Logger } from '@kbn/core/server'; +import { AnalyticsServiceSetup, AuthenticatedUser, KibanaRequest, Logger } from '@kbn/core/server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { ApiConfig, AttackDiscovery, + AttackDiscoveryPostRequestBody, AttackDiscoveryResponse, AttackDiscoveryStat, AttackDiscoveryStatus, + ExecuteConnectorRequestBody, GenerationInterval, Replacements, } from '@kbn/elastic-assistant-common'; import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -import type { Document } from '@langchain/core/documents'; import { v4 as uuidv4 } from 'uuid'; +import { ActionsClientLlm } from '@kbn/langchain/server'; + import { Moment } from 'moment'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; import moment from 'moment/moment'; import { uniq } from 'lodash/fp'; - +import { PublicMethodsOf } from '@kbn/utility-types'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import { getLlmType } from '../utils'; +import type { GetRegisteredTools } from '../../services/app_context'; import { ATTACK_DISCOVERY_ERROR_EVENT, ATTACK_DISCOVERY_SUCCESS_EVENT, -} from '../../../lib/telemetry/event_based_telemetry'; -import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +} from '../../lib/telemetry/event_based_telemetry'; +import { AssistantToolParams } from '../../types'; +import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ { @@ -44,6 +53,116 @@ export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ }, ]; +export const getAssistantToolParams = ({ + actionsClient, + alertsIndexPattern, + anonymizationFields, + apiConfig, + esClient, + connectorTimeout, + langChainTimeout, + langSmithProject, + langSmithApiKey, + logger, + latestReplacements, + onNewReplacements, + request, + size, +}: { + actionsClient: PublicMethodsOf<ActionsClient>; + alertsIndexPattern: string; + anonymizationFields?: AnonymizationFieldResponse[]; + apiConfig: ApiConfig; + esClient: ElasticsearchClient; + connectorTimeout: number; + langChainTimeout: number; + langSmithProject?: string; + langSmithApiKey?: string; + logger: Logger; + latestReplacements: Replacements; + onNewReplacements: (newReplacements: Replacements) => void; + request: KibanaRequest< + unknown, + unknown, + ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody + >; + size: number; +}) => { + const traceOptions = { + projectName: langSmithProject, + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: langSmithProject, + logger, + }), + ], + }; + + const llm = new ActionsClientLlm({ + actionsClient, + connectorId: apiConfig.connectorId, + llmType: getLlmType(apiConfig.actionTypeId), + logger, + temperature: 0, // zero temperature for attack discovery, because we want structured JSON output + timeout: connectorTimeout, + traceOptions, + }); + + return formatAssistantToolParams({ + alertsIndexPattern, + anonymizationFields, + esClient, + latestReplacements, + langChainTimeout, + llm, + logger, + onNewReplacements, + request, + size, + }); +}; + +const formatAssistantToolParams = ({ + alertsIndexPattern, + anonymizationFields, + esClient, + langChainTimeout, + latestReplacements, + llm, + logger, + onNewReplacements, + request, + size, +}: { + alertsIndexPattern: string; + anonymizationFields?: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + langChainTimeout: number; + latestReplacements: Replacements; + llm: ActionsClientLlm; + logger: Logger; + onNewReplacements: (newReplacements: Replacements) => void; + request: KibanaRequest< + unknown, + unknown, + ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody + >; + size: number; +}): Omit<AssistantToolParams, 'connectorId' | 'inference'> => ({ + alertsIndexPattern, + anonymizationFields: [...(anonymizationFields ?? []), ...REQUIRED_FOR_ATTACK_DISCOVERY], + isEnabledKnowledgeBase: false, // not required for attack discovery + esClient, + langChainTimeout, + llm, + logger, + onNewReplacements, + replacements: latestReplacements, + request, + size, +}); + export const attackDiscoveryStatus: { [k: string]: AttackDiscoveryStatus } = { canceled: 'canceled', failed: 'failed', @@ -68,8 +187,7 @@ export const addGenerationInterval = ( export const updateAttackDiscoveryStatusToRunning = async ( dataClient: AttackDiscoveryDataClient, authenticatedUser: AuthenticatedUser, - apiConfig: ApiConfig, - alertsContextCount: number + apiConfig: ApiConfig ): Promise<{ currentAd: AttackDiscoveryResponse; attackDiscoveryId: string; @@ -81,7 +199,6 @@ export const updateAttackDiscoveryStatusToRunning = async ( const currentAd = foundAttackDiscovery ? await dataClient?.updateAttackDiscovery({ attackDiscoveryUpdateProps: { - alertsContextCount, backingIndex: foundAttackDiscovery.backingIndex, id: foundAttackDiscovery.id, status: attackDiscoveryStatus.running, @@ -90,7 +207,6 @@ export const updateAttackDiscoveryStatusToRunning = async ( }) : await dataClient?.createAttackDiscovery({ attackDiscoveryCreate: { - alertsContextCount, apiConfig, attackDiscoveries: [], status: attackDiscoveryStatus.running, @@ -145,32 +261,38 @@ export const updateAttackDiscoveryStatusToCanceled = async ( return updatedAttackDiscovery; }; +const getDataFromJSON = (adStringified: string) => { + const { alertsContextCount, attackDiscoveries } = JSON.parse(adStringified); + return { alertsContextCount, attackDiscoveries }; +}; + export const updateAttackDiscoveries = async ({ - anonymizedAlerts, apiConfig, - attackDiscoveries, attackDiscoveryId, authenticatedUser, dataClient, latestReplacements, logger, + rawAttackDiscoveries, size, startTime, telemetry, }: { - anonymizedAlerts: Document[]; apiConfig: ApiConfig; - attackDiscoveries: AttackDiscovery[] | null; attackDiscoveryId: string; authenticatedUser: AuthenticatedUser; dataClient: AttackDiscoveryDataClient; latestReplacements: Replacements; logger: Logger; + rawAttackDiscoveries: string | null; size: number; startTime: Moment; telemetry: AnalyticsServiceSetup; }) => { try { + if (rawAttackDiscoveries == null) { + throw new Error('tool returned no attack discoveries'); + } const currentAd = await dataClient.getAttackDiscovery({ id: attackDiscoveryId, authenticatedUser, @@ -180,12 +302,12 @@ export const updateAttackDiscoveries = async ({ } const endTime = moment(); const durationMs = endTime.diff(startTime); - const alertsContextCount = anonymizedAlerts.length; + const { alertsContextCount, attackDiscoveries } = getDataFromJSON(rawAttackDiscoveries); const updateProps = { alertsContextCount, - attackDiscoveries: attackDiscoveries ?? undefined, + attackDiscoveries, status: attackDiscoveryStatus.succeeded, - ...(alertsContextCount === 0 + ...(alertsContextCount === 0 || attackDiscoveries === 0 ? {} : { generationIntervals: addGenerationInterval(currentAd.generationIntervals, { @@ -205,14 +327,13 @@ export const updateAttackDiscoveries = async ({ telemetry.reportEvent(ATTACK_DISCOVERY_SUCCESS_EVENT.eventType, { actionTypeId: apiConfig.actionTypeId, alertsContextCount: updateProps.alertsContextCount, - alertsCount: - uniq( - updateProps.attackDiscoveries?.flatMap( - (attackDiscovery: AttackDiscovery) => attackDiscovery.alertIds - ) - ).length ?? 0, + alertsCount: uniq( + updateProps.attackDiscoveries.flatMap( + (attackDiscovery: AttackDiscovery) => attackDiscovery.alertIds + ) + ).length, configuredAlertsCount: size, - discoveriesGenerated: updateProps.attackDiscoveries?.length ?? 0, + discoveriesGenerated: updateProps.attackDiscoveries.length, durationMs, model: apiConfig.model, provider: apiConfig.provider, @@ -229,6 +350,70 @@ export const updateAttackDiscoveries = async ({ } }; +export const handleToolError = async ({ + apiConfig, + attackDiscoveryId, + authenticatedUser, + dataClient, + err, + latestReplacements, + logger, + telemetry, +}: { + apiConfig: ApiConfig; + attackDiscoveryId: string; + authenticatedUser: AuthenticatedUser; + dataClient: AttackDiscoveryDataClient; + err: Error; + latestReplacements: Replacements; + logger: Logger; + telemetry: AnalyticsServiceSetup; +}) => { + try { + logger.error(err); + const error = transformError(err); + const currentAd = await dataClient.getAttackDiscovery({ + id: attackDiscoveryId, + authenticatedUser, + }); + + if (currentAd === null || currentAd?.status === 'canceled') { + return; + } + await dataClient.updateAttackDiscovery({ + attackDiscoveryUpdateProps: { + attackDiscoveries: [], + status: attackDiscoveryStatus.failed, + id: attackDiscoveryId, + replacements: latestReplacements, + backingIndex: currentAd.backingIndex, + failureReason: error.message, + }, + authenticatedUser, + }); + telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { + actionTypeId: apiConfig.actionTypeId, + errorMessage: error.message, + model: apiConfig.model, + provider: apiConfig.provider, + }); + } catch (updateErr) { + const updateError = transformError(updateErr); + telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { + actionTypeId: apiConfig.actionTypeId, + errorMessage: updateError.message, + model: apiConfig.model, + provider: apiConfig.provider, + }); + } +}; + +export const getAssistantTool = (getRegisteredTools: GetRegisteredTools, pluginName: string) => { + // get the attack discovery tool: + const assistantTools = getRegisteredTools(pluginName); + return assistantTools.find((tool) => tool.id === 'attack-discovery'); +}; + export const updateAttackDiscoveryLastViewedAt = async ({ connectorId, authenticatedUser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts deleted file mode 100644 index 2e0a545eb083a..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AuthenticatedUser } from '@kbn/core-security-common'; - -import { getAttackDiscoveryStats } from './helpers'; -import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; -import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; - -jest.mock('lodash/fp', () => ({ - uniq: jest.fn((arr) => Array.from(new Set(arr))), -})); - -jest.mock('@kbn/securitysolution-es-utils', () => ({ - transformError: jest.fn((err) => err), -})); -jest.mock('@kbn/langchain/server', () => ({ - ActionsClientLlm: jest.fn(), -})); -jest.mock('../../evaluate/utils', () => ({ - getLangSmithTracer: jest.fn().mockReturnValue([]), -})); -jest.mock('../../utils', () => ({ - getLlmType: jest.fn().mockReturnValue('llm-type'), -})); -const findAttackDiscoveryByConnectorId = jest.fn(); -const updateAttackDiscovery = jest.fn(); -const createAttackDiscovery = jest.fn(); -const getAttackDiscovery = jest.fn(); -const findAllAttackDiscoveries = jest.fn(); -const mockDataClient = { - findAttackDiscoveryByConnectorId, - updateAttackDiscovery, - createAttackDiscovery, - getAttackDiscovery, - findAllAttackDiscoveries, -} as unknown as AttackDiscoveryDataClient; - -const mockAuthenticatedUser = { - username: 'user', - profile_uid: '1234', - authentication_realm: { - type: 'my_realm_type', - name: 'my_realm_name', - }, -} as AuthenticatedUser; - -const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; - -describe('helpers', () => { - const date = '2024-03-28T22:27:28.000Z'; - beforeAll(() => { - jest.useFakeTimers(); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - beforeEach(() => { - jest.clearAllMocks(); - jest.setSystemTime(new Date(date)); - getAttackDiscovery.mockResolvedValue(mockCurrentAd); - updateAttackDiscovery.mockResolvedValue({}); - }); - - describe('getAttackDiscoveryStats', () => { - const mockDiscoveries = [ - { - timestamp: '2024-06-13T17:55:11.360Z', - id: '8abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:55:11.360Z', - updatedAt: '2024-06-17T20:47:57.556Z', - lastViewedAt: '2024-06-17T20:47:57.556Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'failed', - alertsContextCount: undefined, - apiConfig: { - connectorId: 'my-bedrock-old', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: - 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', - }, - { - timestamp: '2024-06-13T17:55:11.360Z', - id: '9abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:55:11.360Z', - updatedAt: '2024-06-17T20:47:57.556Z', - lastViewedAt: '2024-06-17T20:46:57.556Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'failed', - alertsContextCount: undefined, - apiConfig: { - connectorId: 'my-bedrock-old', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: - 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', - }, - { - timestamp: '2024-06-12T19:54:50.428Z', - id: '745e005b-7248-4c08-b8b6-4cad263b4be0', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T19:54:50.428Z', - updatedAt: '2024-06-17T20:47:27.182Z', - lastViewedAt: '2024-06-17T20:27:27.182Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'running', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-gen-ai', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-13T17:50:59.409Z', - id: 'f48da2ca-b63e-4387-82d7-1423a68500aa', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:50:59.409Z', - updatedAt: '2024-06-17T20:47:59.969Z', - lastViewedAt: '2024-06-17T20:47:35.227Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'succeeded', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-gpt4o-ai', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-12T21:18:56.377Z', - id: '82fced1d-de48-42db-9f56-e45122dee017', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T21:18:56.377Z', - updatedAt: '2024-06-17T20:47:39.372Z', - lastViewedAt: '2024-06-17T20:47:39.372Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'canceled', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-bedrock', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-12T16:44:23.107Z', - id: 'a4709094-6116-484b-b096-1e8d151cb4b7', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T16:44:23.107Z', - updatedAt: '2024-06-17T20:48:16.961Z', - lastViewedAt: '2024-06-17T20:47:16.961Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'succeeded', - alertsContextCount: 0, - apiConfig: { - connectorId: 'my-gen-a2i', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [ - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: 'steph threw an error', - }, - ]; - beforeEach(() => { - findAllAttackDiscoveries.mockResolvedValue(mockDiscoveries); - }); - it('returns the formatted stats object', async () => { - const stats = await getAttackDiscoveryStats({ - authenticatedUser: mockAuthenticatedUser, - dataClient: mockDataClient, - }); - expect(stats).toEqual([ - { - hasViewed: true, - status: 'failed', - count: 0, - connectorId: 'my-bedrock-old', - }, - { - hasViewed: false, - status: 'failed', - count: 0, - connectorId: 'my-bedrock-old', - }, - { - hasViewed: false, - status: 'running', - count: 1, - connectorId: 'my-gen-ai', - }, - { - hasViewed: false, - status: 'succeeded', - count: 1, - connectorId: 'my-gpt4o-ai', - }, - { - hasViewed: true, - status: 'canceled', - count: 1, - connectorId: 'my-bedrock', - }, - { - hasViewed: false, - status: 'succeeded', - count: 4, - connectorId: 'my-gen-a2i', - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx deleted file mode 100644 index e58b67bdcc1ad..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AnalyticsServiceSetup, AuthenticatedUser, Logger } from '@kbn/core/server'; -import { ApiConfig, Replacements } from '@kbn/elastic-assistant-common'; -import { transformError } from '@kbn/securitysolution-es-utils'; - -import { AttackDiscoveryDataClient } from '../../../../../lib/attack_discovery/persistence'; -import { attackDiscoveryStatus } from '../../../helpers/helpers'; -import { ATTACK_DISCOVERY_ERROR_EVENT } from '../../../../../lib/telemetry/event_based_telemetry'; - -export const handleGraphError = async ({ - apiConfig, - attackDiscoveryId, - authenticatedUser, - dataClient, - err, - latestReplacements, - logger, - telemetry, -}: { - apiConfig: ApiConfig; - attackDiscoveryId: string; - authenticatedUser: AuthenticatedUser; - dataClient: AttackDiscoveryDataClient; - err: Error; - latestReplacements: Replacements; - logger: Logger; - telemetry: AnalyticsServiceSetup; -}) => { - try { - logger.error(err); - const error = transformError(err); - const currentAd = await dataClient.getAttackDiscovery({ - id: attackDiscoveryId, - authenticatedUser, - }); - - if (currentAd === null || currentAd?.status === 'canceled') { - return; - } - - await dataClient.updateAttackDiscovery({ - attackDiscoveryUpdateProps: { - attackDiscoveries: [], - status: attackDiscoveryStatus.failed, - id: attackDiscoveryId, - replacements: latestReplacements, - backingIndex: currentAd.backingIndex, - failureReason: error.message, - }, - authenticatedUser, - }); - telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { - actionTypeId: apiConfig.actionTypeId, - errorMessage: error.message, - model: apiConfig.model, - provider: apiConfig.provider, - }); - } catch (updateErr) { - const updateError = transformError(updateErr); - telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { - actionTypeId: apiConfig.actionTypeId, - errorMessage: updateError.message, - model: apiConfig.model, - provider: apiConfig.provider, - }); - } -}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx deleted file mode 100644 index 8a8c49f796500..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ActionsClient } from '@kbn/actions-plugin/server'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { Logger } from '@kbn/core/server'; -import { ApiConfig, AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -import { ActionsClientLlm } from '@kbn/langchain/server'; -import { PublicMethodsOf } from '@kbn/utility-types'; -import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; -import type { Document } from '@langchain/core/documents'; - -import { getDefaultAttackDiscoveryGraph } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph'; -import { - ATTACK_DISCOVERY_GRAPH_RUN_NAME, - ATTACK_DISCOVERY_TAG, -} from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/constants'; -import { GraphState } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/types'; -import { throwIfErrorCountsExceeded } from '../throw_if_error_counts_exceeded'; -import { getLlmType } from '../../../../utils'; - -export const invokeAttackDiscoveryGraph = async ({ - actionsClient, - alertsIndexPattern, - anonymizationFields, - apiConfig, - connectorTimeout, - esClient, - langSmithProject, - langSmithApiKey, - latestReplacements, - logger, - onNewReplacements, - size, -}: { - actionsClient: PublicMethodsOf<ActionsClient>; - alertsIndexPattern: string; - anonymizationFields: AnonymizationFieldResponse[]; - apiConfig: ApiConfig; - connectorTimeout: number; - esClient: ElasticsearchClient; - langSmithProject?: string; - langSmithApiKey?: string; - latestReplacements: Replacements; - logger: Logger; - onNewReplacements: (newReplacements: Replacements) => void; - size: number; -}): Promise<{ - anonymizedAlerts: Document[]; - attackDiscoveries: AttackDiscovery[] | null; -}> => { - const llmType = getLlmType(apiConfig.actionTypeId); - const model = apiConfig.model; - const tags = [ATTACK_DISCOVERY_TAG, llmType, model].flatMap((tag) => tag ?? []); - - const traceOptions = { - projectName: langSmithProject, - tracers: [ - ...getLangSmithTracer({ - apiKey: langSmithApiKey, - projectName: langSmithProject, - logger, - }), - ], - }; - - const llm = new ActionsClientLlm({ - actionsClient, - connectorId: apiConfig.connectorId, - llmType, - logger, - temperature: 0, // zero temperature for attack discovery, because we want structured JSON output - timeout: connectorTimeout, - traceOptions, - }); - - if (llm == null) { - throw new Error('LLM is required for attack discoveries'); - } - - const graph = getDefaultAttackDiscoveryGraph({ - alertsIndexPattern, - anonymizationFields, - esClient, - llm, - logger, - onNewReplacements, - replacements: latestReplacements, - size, - }); - - logger?.debug(() => 'invokeAttackDiscoveryGraph: invoking the Attack discovery graph'); - - const result: GraphState = await graph.invoke( - {}, - { - callbacks: [...(traceOptions?.tracers ?? [])], - runName: ATTACK_DISCOVERY_GRAPH_RUN_NAME, - tags, - } - ); - const { - attackDiscoveries, - anonymizedAlerts, - errors, - generationAttempts, - hallucinationFailures, - maxGenerationAttempts, - maxHallucinationFailures, - } = result; - - throwIfErrorCountsExceeded({ - errors, - generationAttempts, - hallucinationFailures, - logger, - maxGenerationAttempts, - maxHallucinationFailures, - }); - - return { anonymizedAlerts, attackDiscoveries }; -}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx deleted file mode 100644 index 9cbf3fa06510d..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; - -import { mockAnonymizationFields } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields'; -import { requestIsValid } from '.'; - -describe('requestIsValid', () => { - const alertsIndexPattern = '.alerts-security.alerts-default'; - const replacements = { uuid: 'original_value' }; - const size = 20; - const request = { - body: { - actionTypeId: '.bedrock', - alertsIndexPattern, - anonymizationFields: mockAnonymizationFields, - connectorId: 'test-connector-id', - replacements, - size, - subAction: 'invokeAI', - }, - } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; - - it('returns false when the request is missing required anonymization parameters', () => { - const requestMissingAnonymizationParams = { - body: { - alertsIndexPattern: '.alerts-security.alerts-default', - isEnabledKnowledgeBase: false, - size: 20, - }, - } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; - - const params = { - alertsIndexPattern, - request: requestMissingAnonymizationParams, // <-- missing required anonymization parameters - size, - }; - - expect(requestIsValid(params)).toBe(false); - }); - - it('returns false when the alertsIndexPattern is undefined', () => { - const params = { - alertsIndexPattern: undefined, // <-- alertsIndexPattern is undefined - request, - size, - }; - - expect(requestIsValid(params)).toBe(false); - }); - - it('returns false when size is undefined', () => { - const params = { - alertsIndexPattern, - request, - size: undefined, // <-- size is undefined - }; - - expect(requestIsValid(params)).toBe(false); - }); - - it('returns false when size is out of range', () => { - const params = { - alertsIndexPattern, - request, - size: 0, // <-- size is out of range - }; - - expect(requestIsValid(params)).toBe(false); - }); - - it('returns true if all required params are provided', () => { - const params = { - alertsIndexPattern, - request, - size, - }; - - expect(requestIsValid(params)).toBe(true); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx deleted file mode 100644 index 36487d8f6b3e2..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { KibanaRequest } from '@kbn/core/server'; -import { - AttackDiscoveryPostRequestBody, - ExecuteConnectorRequestBody, - sizeIsOutOfRange, -} from '@kbn/elastic-assistant-common'; - -import { requestHasRequiredAnonymizationParams } from '../../../../../lib/langchain/helpers'; - -export const requestIsValid = ({ - alertsIndexPattern, - request, - size, -}: { - alertsIndexPattern: string | undefined; - request: KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >; - size: number | undefined; -}): boolean => - requestHasRequiredAnonymizationParams(request) && - alertsIndexPattern != null && - size != null && - !sizeIsOutOfRange(size); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts deleted file mode 100644 index 409ee2da74cd2..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Logger } from '@kbn/core/server'; - -import * as i18n from './translations'; - -export const throwIfErrorCountsExceeded = ({ - errors, - generationAttempts, - hallucinationFailures, - logger, - maxGenerationAttempts, - maxHallucinationFailures, -}: { - errors: string[]; - generationAttempts: number; - hallucinationFailures: number; - logger?: Logger; - maxGenerationAttempts: number; - maxHallucinationFailures: number; -}): void => { - if (hallucinationFailures >= maxHallucinationFailures) { - const hallucinationFailuresError = `${i18n.MAX_HALLUCINATION_FAILURES( - hallucinationFailures - )}\n${errors.join(',\n')}`; - - logger?.error(hallucinationFailuresError); - throw new Error(hallucinationFailuresError); - } - - if (generationAttempts >= maxGenerationAttempts) { - const generationAttemptsError = `${i18n.MAX_GENERATION_ATTEMPTS( - generationAttempts - )}\n${errors.join(',\n')}`; - - logger?.error(generationAttemptsError); - throw new Error(generationAttemptsError); - } -}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts deleted file mode 100644 index fbe06d0e73b2a..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const MAX_HALLUCINATION_FAILURES = (hallucinationFailures: number) => - i18n.translate( - 'xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxHallucinationFailuresErrorMessage', - { - defaultMessage: - 'Maximum hallucination failures ({hallucinationFailures}) reached. Try sending fewer alerts to this model.', - values: { hallucinationFailures }, - } - ); - -export const MAX_GENERATION_ATTEMPTS = (generationAttempts: number) => - i18n.translate( - 'xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxGenerationAttemptsErrorMessage', - { - defaultMessage: - 'Maximum generation attempts ({generationAttempts}) reached. Try sending fewer alerts to this model.', - values: { generationAttempts }, - } - ); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts similarity index 79% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts index d50987317b0e3..cbd3e6063fbd2 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts @@ -7,27 +7,22 @@ import { AuthenticatedUser } from '@kbn/core-security-common'; import { postAttackDiscoveryRoute } from './post_attack_discovery'; -import { serverMock } from '../../../__mocks__/server'; -import { requestContextMock } from '../../../__mocks__/request_context'; +import { serverMock } from '../../__mocks__/server'; +import { requestContextMock } from '../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; -import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; -import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; -import { postAttackDiscoveryRequest } from '../../../__mocks__/request'; +import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; +import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { postAttackDiscoveryRequest } from '../../__mocks__/request'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; import { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; - -import { updateAttackDiscoveryStatusToRunning } from '../helpers/helpers'; - -jest.mock('../helpers/helpers', () => { - const original = jest.requireActual('../helpers/helpers'); - - return { - ...original, - updateAttackDiscoveryStatusToRunning: jest.fn(), - }; -}); +import { + getAssistantTool, + getAssistantToolParams, + updateAttackDiscoveryStatusToRunning, +} from './helpers'; +jest.mock('./helpers'); const { clients, context } = requestContextMock.createTools(); const server: ReturnType<typeof serverMock.create> = serverMock.create(); @@ -77,6 +72,8 @@ describe('postAttackDiscoveryRoute', () => { context.elasticAssistant.actions = actionsMock.createStart(); postAttackDiscoveryRoute(server.router); findAttackDiscoveryByConnectorId.mockResolvedValue(mockCurrentAd); + (getAssistantTool as jest.Mock).mockReturnValue({ getTool: jest.fn() }); + (getAssistantToolParams as jest.Mock).mockReturnValue({ tool: 'tool' }); (updateAttackDiscoveryStatusToRunning as jest.Mock).mockResolvedValue({ currentAd: runningAd, attackDiscoveryId: mockCurrentAd.id, @@ -120,6 +117,15 @@ describe('postAttackDiscoveryRoute', () => { }); }); + it('should handle assistantTool null response', async () => { + (getAssistantTool as jest.Mock).mockReturnValue(null); + const response = await server.inject( + postAttackDiscoveryRequest(mockRequestBody), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(404); + }); + it('should handle updateAttackDiscoveryStatusToRunning error', async () => { (updateAttackDiscoveryStatusToRunning as jest.Mock).mockRejectedValue(new Error('Oh no!')); const response = await server.inject( diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts similarity index 79% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts index b0273741bdf5e..b9c680dde3d1d 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { type IKibanaResponse, IRouter, Logger } from '@kbn/core/server'; import { AttackDiscoveryPostRequestBody, @@ -12,17 +13,20 @@ import { ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION, Replacements, } from '@kbn/elastic-assistant-common'; -import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { transformError } from '@kbn/securitysolution-es-utils'; import moment from 'moment/moment'; -import { ATTACK_DISCOVERY } from '../../../../common/constants'; -import { handleGraphError } from './helpers/handle_graph_error'; -import { updateAttackDiscoveries, updateAttackDiscoveryStatusToRunning } from '../helpers/helpers'; -import { buildResponse } from '../../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../../types'; -import { invokeAttackDiscoveryGraph } from './helpers/invoke_attack_discovery_graph'; -import { requestIsValid } from './helpers/request_is_valid'; +import { ATTACK_DISCOVERY } from '../../../common/constants'; +import { + getAssistantTool, + getAssistantToolParams, + handleToolError, + updateAttackDiscoveries, + updateAttackDiscoveryStatusToRunning, +} from './helpers'; +import { DEFAULT_PLUGIN_NAME, getPluginNameFromRequest } from '../helpers'; +import { buildResponse } from '../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../types'; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds @@ -81,6 +85,11 @@ export const postAttackDiscoveryRoute = ( statusCode: 500, }); } + const pluginName = getPluginNameFromRequest({ + request, + defaultPluginName: DEFAULT_PLUGIN_NAME, + logger, + }); // get parameters from the request body const alertsIndexPattern = decodeURIComponent(request.body.alertsIndexPattern); @@ -93,19 +102,6 @@ export const postAttackDiscoveryRoute = ( size, } = request.body; - if ( - !requestIsValid({ - alertsIndexPattern, - request, - size, - }) - ) { - return resp.error({ - body: 'Bad Request', - statusCode: 400, - }); - } - // get an Elasticsearch client for the authenticated user: const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -115,45 +111,59 @@ export const postAttackDiscoveryRoute = ( latestReplacements = { ...latestReplacements, ...newReplacements }; }; - const { currentAd, attackDiscoveryId } = await updateAttackDiscoveryStatusToRunning( - dataClient, - authenticatedUser, - apiConfig, - size + const assistantTool = getAssistantTool( + (await context.elasticAssistant).getRegisteredTools, + pluginName ); - // Don't await the results of invoking the graph; (just the metadata will be returned from the route handler): - invokeAttackDiscoveryGraph({ + if (!assistantTool) { + return response.notFound(); // attack discovery tool not found + } + + const assistantToolParams = getAssistantToolParams({ actionsClient, alertsIndexPattern, anonymizationFields, apiConfig, - connectorTimeout: CONNECTOR_TIMEOUT, esClient, + latestReplacements, + connectorTimeout: CONNECTOR_TIMEOUT, + langChainTimeout: LANG_CHAIN_TIMEOUT, langSmithProject, langSmithApiKey, - latestReplacements, logger, onNewReplacements, + request, size, - }) - .then(({ anonymizedAlerts, attackDiscoveries }) => + }); + + // invoke the attack discovery tool: + const toolInstance = assistantTool.getTool(assistantToolParams); + + const { currentAd, attackDiscoveryId } = await updateAttackDiscoveryStatusToRunning( + dataClient, + authenticatedUser, + apiConfig + ); + + toolInstance + ?.invoke('') + .then((rawAttackDiscoveries: string) => updateAttackDiscoveries({ - anonymizedAlerts, apiConfig, - attackDiscoveries, attackDiscoveryId, authenticatedUser, dataClient, latestReplacements, logger, + rawAttackDiscoveries, size, startTime, telemetry, }) ) .catch((err) => - handleGraphError({ + handleToolError({ apiConfig, attackDiscoveryId, authenticatedUser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts deleted file mode 100644 index c0320c9ff6adf..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - ASSISTANT_GRAPH_MAP, - AssistantGraphMetadata, - AttackDiscoveryGraphMetadata, -} from '../../../lib/langchain/graphs'; - -export interface GetGraphsFromNamesResults { - attackDiscoveryGraphs: AttackDiscoveryGraphMetadata[]; - assistantGraphs: AssistantGraphMetadata[]; -} - -export const getGraphsFromNames = (graphNames: string[]): GetGraphsFromNamesResults => - graphNames.reduce<GetGraphsFromNamesResults>( - (acc, graphName) => { - const graph = ASSISTANT_GRAPH_MAP[graphName]; - if (graph != null) { - return graph.graphType === 'assistant' - ? { ...acc, assistantGraphs: [...acc.assistantGraphs, graph] } - : { ...acc, attackDiscoveryGraphs: [...acc.attackDiscoveryGraphs, graph] }; - } - - return acc; - }, - { - attackDiscoveryGraphs: [], - assistantGraphs: [], - } - ); diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index eb12946a9b61f..29a7527964677 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -29,7 +29,6 @@ import { createStructuredChatAgent, createToolCallingAgent, } from 'langchain/agents'; -import { omit } from 'lodash/fp'; import { buildResponse } from '../../lib/build_response'; import { AssistantDataClients } from '../../lib/langchain/executors/types'; import { AssistantToolParams, ElasticAssistantRequestHandlerContext, GetElser } from '../../types'; @@ -37,7 +36,6 @@ import { DEFAULT_PLUGIN_NAME, isV2KnowledgeBaseEnabled, performChecks } from '.. import { fetchLangSmithDataset } from './utils'; import { transformESSearchToAnonymizationFields } from '../../ai_assistant_data_clients/anonymization_fields/helpers'; import { EsAnonymizationFieldsSchema } from '../../ai_assistant_data_clients/anonymization_fields/types'; -import { evaluateAttackDiscovery } from '../../lib/attack_discovery/evaluation'; import { DefaultAssistantGraph, getDefaultAssistantGraph, @@ -49,12 +47,9 @@ import { structuredChatAgentPrompt, } from '../../lib/langchain/graphs/default_assistant_graph/prompts'; import { getLlmClass, getLlmType, isOpenSourceModel } from '../utils'; -import { getGraphsFromNames } from './get_graphs_from_names'; const DEFAULT_SIZE = 20; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes -const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds -const CONNECTOR_TIMEOUT = LANG_CHAIN_TIMEOUT - 10_000; // 9 minutes 40 seconds export const postEvaluateRoute = ( router: IRouter<ElasticAssistantRequestHandlerContext>, @@ -111,10 +106,8 @@ export const postEvaluateRoute = ( const { alertsIndexPattern, datasetName, - evaluatorConnectorId, graphs: graphNames, langSmithApiKey, - langSmithProject, connectorIds, size, replacements, @@ -131,9 +124,7 @@ export const postEvaluateRoute = ( logger.info('postEvaluateRoute:'); logger.info(`request.query:\n${JSON.stringify(request.query, null, 2)}`); - logger.info( - `request.body:\n${JSON.stringify(omit(['langSmithApiKey'], request.body), null, 2)}` - ); + logger.info(`request.body:\n${JSON.stringify(request.body, null, 2)}`); logger.info(`Evaluation ID: ${evaluationId}`); const totalExecutions = connectorIds.length * graphNames.length * dataset.length; @@ -179,38 +170,6 @@ export const postEvaluateRoute = ( // Fetch any tools registered to the security assistant const assistantTools = assistantContext.getRegisteredTools(DEFAULT_PLUGIN_NAME); - const { attackDiscoveryGraphs } = getGraphsFromNames(graphNames); - - if (attackDiscoveryGraphs.length > 0) { - try { - // NOTE: we don't wait for the evaluation to finish here, because - // the client will retry / timeout when evaluations take too long - void evaluateAttackDiscovery({ - actionsClient, - alertsIndexPattern, - attackDiscoveryGraphs, - connectors, - connectorTimeout: CONNECTOR_TIMEOUT, - datasetName, - esClient, - evaluationId, - evaluatorConnectorId, - langSmithApiKey, - langSmithProject, - logger, - runName, - size, - }); - } catch (err) { - logger.error(() => `Error evaluating attack discovery: ${err}`); - } - - // Return early if we're only running attack discovery graphs - return response.ok({ - body: { evaluationId, success: true }, - }); - } - const graphs: Array<{ name: string; graph: DefaultAssistantGraph; diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts index 0260c47b4bd29..34f009e266515 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts @@ -21,7 +21,7 @@ export const fetchLangSmithDataset = async ( logger: Logger, langSmithApiKey?: string ): Promise<Example[]> => { - if (datasetName === undefined || (langSmithApiKey == null && !isLangSmithEnabled())) { + if (datasetName === undefined || !isLangSmithEnabled()) { throw new Error('LangSmith dataset name not provided or LangSmith not enabled'); } diff --git a/x-pack/plugins/elastic_assistant/server/routes/index.ts b/x-pack/plugins/elastic_assistant/server/routes/index.ts index a6d7a4298c2b7..43e1229250f46 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/index.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/index.ts @@ -9,8 +9,8 @@ export { postActionsConnectorExecuteRoute } from './post_actions_connector_execute'; // Attack Discovery -export { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery'; -export { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery'; +export { postAttackDiscoveryRoute } from './attack_discovery/post_attack_discovery'; +export { getAttackDiscoveryRoute } from './attack_discovery/get_attack_discovery'; // Knowledge Base export { deleteKnowledgeBaseRoute } from './knowledge_base/delete_knowledge_base'; diff --git a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts index 7898629e15b5c..56eb9760e442a 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts @@ -7,9 +7,9 @@ import type { Logger } from '@kbn/core/server'; -import { cancelAttackDiscoveryRoute } from './attack_discovery/post/cancel/cancel_attack_discovery'; -import { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery'; -import { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery'; +import { cancelAttackDiscoveryRoute } from './attack_discovery/cancel_attack_discovery'; +import { getAttackDiscoveryRoute } from './attack_discovery/get_attack_discovery'; +import { postAttackDiscoveryRoute } from './attack_discovery/post_attack_discovery'; import { ElasticAssistantPluginRouter, GetElser } from '../types'; import { createConversationRoute } from './user_conversations/create_route'; import { deleteConversationRoute } from './user_conversations/delete_route'; diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index e84b97ab43d7a..45bd5a4149b58 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -43,10 +43,10 @@ import { ActionsClientGeminiChatModel, ActionsClientLlm, } from '@kbn/langchain/server'; -import type { InferenceServerStart } from '@kbn/inference-plugin/server'; +import type { InferenceServerStart } from '@kbn/inference-plugin/server'; import type { GetAIAssistantKnowledgeBaseDataClientParams } from './ai_assistant_data_clients/knowledge_base'; -import { AttackDiscoveryDataClient } from './lib/attack_discovery/persistence'; +import { AttackDiscoveryDataClient } from './ai_assistant_data_clients/attack_discovery'; import { AIAssistantConversationsDataClient } from './ai_assistant_data_clients/conversations'; import type { GetRegisteredFeatures, GetRegisteredTools } from './services/app_context'; import { AIAssistantDataClient } from './ai_assistant_data_clients'; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx index dd995d115b6c3..885ab18c879a7 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx @@ -6,11 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import { - replaceAnonymizedValuesWithOriginalValues, - type AttackDiscovery, - type Replacements, -} from '@kbn/elastic-assistant-common'; +import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; import React, { useMemo } from 'react'; import { AttackDiscoveryMarkdownFormatter } from '../../attack_discovery_markdown_formatter'; @@ -27,41 +23,26 @@ const ActionableSummaryComponent: React.FC<Props> = ({ replacements, showAnonymized = false, }) => { - const entitySummary = useMemo( + const entitySummaryMarkdownWithReplacements = useMemo( () => - showAnonymized - ? attackDiscovery.entitySummaryMarkdown - : replaceAnonymizedValuesWithOriginalValues({ - messageContent: attackDiscovery.entitySummaryMarkdown ?? '', - replacements: { ...replacements }, - }), - - [attackDiscovery.entitySummaryMarkdown, replacements, showAnonymized] - ); - - // title will be used as a fallback if entitySummaryMarkdown is empty - const title = useMemo( - () => - showAnonymized - ? attackDiscovery.title - : replaceAnonymizedValuesWithOriginalValues({ - messageContent: attackDiscovery.title, - replacements: { ...replacements }, - }), - - [attackDiscovery.title, replacements, showAnonymized] + Object.entries(replacements ?? {}).reduce( + (acc, [key, value]) => acc.replace(key, value), + attackDiscovery.entitySummaryMarkdown + ), + [attackDiscovery.entitySummaryMarkdown, replacements] ); - const entitySummaryOrTitle = - entitySummary != null && entitySummary.length > 0 ? entitySummary : title; - return ( <EuiPanel color="subdued" data-test-subj="actionableSummary"> <EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween"> <EuiFlexItem data-test-subj="entitySummaryMarkdown" grow={false}> <AttackDiscoveryMarkdownFormatter disableActions={showAnonymized} - markdown={entitySummaryOrTitle} + markdown={ + showAnonymized + ? attackDiscovery.entitySummaryMarkdown + : entitySummaryMarkdownWithReplacements + } /> </EuiFlexItem> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx index c6ac9c70e8413..2aaac0449886a 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx @@ -49,15 +49,8 @@ const AttackDiscoveryPanelComponent: React.FC<Props> = ({ ); const buttonContent = useMemo( - () => ( - <Title - isLoading={false} - replacements={replacements} - showAnonymized={showAnonymized} - title={attackDiscovery.title} - /> - ), - [attackDiscovery.title, replacements, showAnonymized] + () => <Title isLoading={false} title={attackDiscovery.title} />, + [attackDiscovery.title] ); return ( diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx index 13326a07adc70..4b0375e4fe503 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx @@ -7,41 +7,20 @@ import { EuiFlexGroup, EuiFlexItem, EuiSkeletonTitle, EuiTitle, useEuiTheme } from '@elastic/eui'; import { AssistantAvatar } from '@kbn/elastic-assistant'; -import { - replaceAnonymizedValuesWithOriginalValues, - type Replacements, -} from '@kbn/elastic-assistant-common'; import { css } from '@emotion/react'; -import React, { useMemo } from 'react'; +import React from 'react'; const AVATAR_SIZE = 24; // px interface Props { isLoading: boolean; - replacements?: Replacements; - showAnonymized?: boolean; title: string; } -const TitleComponent: React.FC<Props> = ({ - isLoading, - replacements, - showAnonymized = false, - title, -}) => { +const TitleComponent: React.FC<Props> = ({ isLoading, title }) => { const { euiTheme } = useEuiTheme(); - const titleWithReplacements = useMemo( - () => - replaceAnonymizedValuesWithOriginalValues({ - messageContent: title, - replacements: { ...replacements }, - }), - - [replacements, title] - ); - return ( <EuiFlexGroup alignItems="center" data-test-subj="title" gutterSize="s"> <EuiFlexItem @@ -74,7 +53,7 @@ const TitleComponent: React.FC<Props> = ({ /> ) : ( <EuiTitle data-test-subj="titleText" size="xs"> - <h2>{showAnonymized ? title : titleWithReplacements}</h2> + <h2>{title}</h2> </EuiTitle> )} </EuiFlexItem> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts b/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts index 0ae524c25ee95..5309ef1de6bb2 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts @@ -56,7 +56,7 @@ export const getAttackDiscoveryMarkdown = ({ replacements?: Replacements; }): string => { const title = getMarkdownFields(attackDiscovery.title); - const entitySummaryMarkdown = getMarkdownFields(attackDiscovery.entitySummaryMarkdown ?? ''); + const entitySummaryMarkdown = getMarkdownFields(attackDiscovery.entitySummaryMarkdown); const summaryMarkdown = getMarkdownFields(attackDiscovery.summaryMarkdown); const detailsMarkdown = getMarkdownFields(attackDiscovery.detailsMarkdown); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx b/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx index ab0a5ac4ede96..874a4d1c99ded 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx @@ -106,9 +106,7 @@ export const usePollApi = ({ ...attackDiscovery, id: attackDiscovery.id ?? uuid.v4(), detailsMarkdown: replaceNewlineLiterals(attackDiscovery.detailsMarkdown), - entitySummaryMarkdown: replaceNewlineLiterals( - attackDiscovery.entitySummaryMarkdown ?? '' - ), + entitySummaryMarkdown: replaceNewlineLiterals(attackDiscovery.entitySummaryMarkdown), summaryMarkdown: replaceNewlineLiterals(attackDiscovery.summaryMarkdown), })), }; @@ -125,7 +123,7 @@ export const usePollApi = ({ const rawResponse = await http.fetch( `/internal/elastic_assistant/attack_discovery/cancel/${connectorId}`, { - method: 'POST', + method: 'PUT', version: ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION, } ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx index 533b95bf7087f..5dd4cb8fc4267 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx @@ -52,7 +52,7 @@ const AnimatedCounterComponent: React.FC<Props> = ({ animationDurationMs = 1000 css={css` height: 32px; margin-right: ${euiTheme.size.xs}; - width: ${count < 100 ? 40 : 60}px; + width: ${count < 100 ? 40 : 53}px; `} data-test-subj="animatedCounter" ref={d3Ref} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx index 0707950383046..56b2205b28726 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx @@ -16,8 +16,6 @@ jest.mock('../../../assistant/use_assistant_availability'); describe('EmptyPrompt', () => { const alertsCount = 20; - const aiConnectorsCount = 2; - const attackDiscoveriesCount = 0; const onGenerate = jest.fn(); beforeEach(() => { @@ -35,8 +33,6 @@ describe('EmptyPrompt', () => { <TestProviders> <EmptyPrompt alertsCount={alertsCount} - aiConnectorsCount={aiConnectorsCount} - attackDiscoveriesCount={attackDiscoveriesCount} isLoading={false} isDisabled={false} onGenerate={onGenerate} @@ -73,34 +69,8 @@ describe('EmptyPrompt', () => { }); describe('when loading is true', () => { - beforeEach(() => { - (useAssistantAvailability as jest.Mock).mockReturnValue({ - hasAssistantPrivilege: true, - isAssistantEnabled: true, - }); - - render( - <TestProviders> - <EmptyPrompt - aiConnectorsCount={2} // <-- non-null - attackDiscoveriesCount={0} // <-- no discoveries - alertsCount={alertsCount} - isLoading={true} // <-- loading - isDisabled={false} - onGenerate={onGenerate} - /> - </TestProviders> - ); - }); - - it('returns null', () => { - const emptyPrompt = screen.queryByTestId('emptyPrompt'); + const isLoading = true; - expect(emptyPrompt).not.toBeInTheDocument(); - }); - }); - - describe('when aiConnectorsCount is null', () => { beforeEach(() => { (useAssistantAvailability as jest.Mock).mockReturnValue({ hasAssistantPrivilege: true, @@ -110,10 +80,8 @@ describe('EmptyPrompt', () => { render( <TestProviders> <EmptyPrompt - aiConnectorsCount={null} // <-- null - attackDiscoveriesCount={0} // <-- no discoveries alertsCount={alertsCount} - isLoading={false} // <-- not loading + isLoading={isLoading} isDisabled={false} onGenerate={onGenerate} /> @@ -121,38 +89,10 @@ describe('EmptyPrompt', () => { ); }); - it('returns null', () => { - const emptyPrompt = screen.queryByTestId('emptyPrompt'); - - expect(emptyPrompt).not.toBeInTheDocument(); - }); - }); - - describe('when there are attack discoveries', () => { - beforeEach(() => { - (useAssistantAvailability as jest.Mock).mockReturnValue({ - hasAssistantPrivilege: true, - isAssistantEnabled: true, - }); - - render( - <TestProviders> - <EmptyPrompt - aiConnectorsCount={2} // <-- non-null - attackDiscoveriesCount={7} // there are discoveries - alertsCount={alertsCount} - isLoading={false} // <-- not loading - isDisabled={false} - onGenerate={onGenerate} - /> - </TestProviders> - ); - }); - - it('returns null', () => { - const emptyPrompt = screen.queryByTestId('emptyPrompt'); + it('disables the generate button while loading', () => { + const generateButton = screen.getByTestId('generate'); - expect(emptyPrompt).not.toBeInTheDocument(); + expect(generateButton).toBeDisabled(); }); }); @@ -169,8 +109,6 @@ describe('EmptyPrompt', () => { <TestProviders> <EmptyPrompt alertsCount={alertsCount} - aiConnectorsCount={2} // <-- non-null - attackDiscoveriesCount={0} // <-- no discoveries isLoading={false} isDisabled={isDisabled} onGenerate={onGenerate} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx index 3d89f5be87030..75c8533efcc92 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx @@ -7,6 +7,7 @@ import { AssistantAvatar } from '@kbn/elastic-assistant'; import { + EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, @@ -14,28 +15,24 @@ import { EuiLink, EuiSpacer, EuiText, + EuiToolTip, useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; import React, { useMemo } from 'react'; import { AnimatedCounter } from './animated_counter'; -import { Generate } from '../generate'; import * as i18n from './translations'; interface Props { - aiConnectorsCount: number | null; // null when connectors are not configured alertsCount: number; - attackDiscoveriesCount: number; isDisabled?: boolean; isLoading: boolean; onGenerate: () => void; } const EmptyPromptComponent: React.FC<Props> = ({ - aiConnectorsCount, alertsCount, - attackDiscoveriesCount, isLoading, isDisabled = false, onGenerate, @@ -113,12 +110,24 @@ const EmptyPromptComponent: React.FC<Props> = ({ ); const actions = useMemo(() => { - return <Generate isLoading={isLoading} isDisabled={isDisabled} onGenerate={onGenerate} />; - }, [isDisabled, isLoading, onGenerate]); + const disabled = isLoading || isDisabled; - if (isLoading || aiConnectorsCount == null || attackDiscoveriesCount > 0) { - return null; - } + return ( + <EuiToolTip + content={disabled ? i18n.SELECT_A_CONNECTOR : null} + data-test-subj="generateTooltip" + > + <EuiButton + color="primary" + data-test-subj="generate" + disabled={disabled} + onClick={onGenerate} + > + {i18n.GENERATE} + </EuiButton> + </EuiToolTip> + ); + }, [isDisabled, isLoading, onGenerate]); return ( <EuiFlexGroup diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts deleted file mode 100644 index e2c7018ef5826..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - showEmptyPrompt, - showFailurePrompt, - showNoAlertsPrompt, - showWelcomePrompt, -} from '../../../helpers'; - -export const showEmptyStates = ({ - aiConnectorsCount, - alertsContextCount, - attackDiscoveriesCount, - connectorId, - failureReason, - isLoading, -}: { - aiConnectorsCount: number | null; - alertsContextCount: number | null; - attackDiscoveriesCount: number; - connectorId: string | undefined; - failureReason: string | null; - isLoading: boolean; -}): boolean => { - const showWelcome = showWelcomePrompt({ aiConnectorsCount, isLoading }); - const showFailure = showFailurePrompt({ connectorId, failureReason, isLoading }); - const showNoAlerts = showNoAlertsPrompt({ alertsContextCount, connectorId, isLoading }); - const showEmpty = showEmptyPrompt({ aiConnectorsCount, attackDiscoveriesCount, isLoading }); - - return showWelcome || showFailure || showNoAlerts || showEmpty; -}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx index 9eacd696a2ff1..3b5b87ada83ec 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { render, screen } from '@testing-library/react'; import React from 'react'; @@ -19,6 +18,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 0; // <-- no connectors configured const alertsContextCount = null; + const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = false; @@ -29,12 +29,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -59,6 +59,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 0; // <-- no alerts to analyze + const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; @@ -69,12 +70,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -103,7 +104,8 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 10; - const attackDiscoveriesCount = 0; + const alertsCount = 10; + const attackDiscoveriesCount = 10; const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -113,12 +115,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={"you're a failure"} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -147,7 +149,8 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 10; - const attackDiscoveriesCount = 0; + const alertsCount = 10; + const attackDiscoveriesCount = 10; const connectorId = 'test-connector-id'; const failureReason = 'this failure should NOT be displayed, because we are loading'; // <-- failureReason is provided const isLoading = true; // <-- loading data @@ -158,12 +161,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={failureReason} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -192,7 +195,8 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const attackDiscoveriesCount = 0; + const alertsCount = 0; // <-- no alerts contributed to attack discoveries + const attackDiscoveriesCount = 0; // <-- no attack discoveries were generated from the alerts const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -202,12 +206,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -236,6 +240,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = null; // <-- no connectors configured const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed + const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = false; @@ -246,12 +251,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -282,6 +287,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 0; // <-- no connectors configured (welcome prompt should be shown if not loading) const alertsContextCount = null; + const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = true; // <-- loading data @@ -292,12 +298,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -332,7 +338,8 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const attackDiscoveriesCount = 7; // <-- attack discoveries are present + const alertsCount = 10; // <-- alerts contributed to attack discoveries + const attackDiscoveriesCount = 3; // <-- attack discoveries were generated from the alerts const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -342,12 +349,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} + alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} - upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx index a083ec7b77fdd..49b4557c72192 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx @@ -9,55 +9,51 @@ import React from 'react'; import { Failure } from '../failure'; import { EmptyPrompt } from '../empty_prompt'; -import { showFailurePrompt, showNoAlertsPrompt, showWelcomePrompt } from '../helpers'; +import { showEmptyPrompt, showNoAlertsPrompt, showWelcomePrompt } from '../helpers'; import { NoAlerts } from '../no_alerts'; import { Welcome } from '../welcome'; interface Props { - aiConnectorsCount: number | null; // null when connectors are not configured - alertsContextCount: number | null; // null when unavailable for the current connector + aiConnectorsCount: number | null; + alertsContextCount: number | null; + alertsCount: number; attackDiscoveriesCount: number; connectorId: string | undefined; failureReason: string | null; isLoading: boolean; onGenerate: () => Promise<void>; - upToAlertsCount: number; } const EmptyStatesComponent: React.FC<Props> = ({ aiConnectorsCount, alertsContextCount, + alertsCount, attackDiscoveriesCount, connectorId, failureReason, isLoading, onGenerate, - upToAlertsCount, }) => { - const isDisabled = connectorId == null; - if (showWelcomePrompt({ aiConnectorsCount, isLoading })) { return <Welcome />; - } - - if (showFailurePrompt({ connectorId, failureReason, isLoading })) { + } else if (!isLoading && failureReason != null) { return <Failure failureReason={failureReason} />; + } else if (showNoAlertsPrompt({ alertsContextCount, isLoading })) { + return <NoAlerts />; + } else if (showEmptyPrompt({ aiConnectorsCount, attackDiscoveriesCount, isLoading })) { + return ( + <EmptyPrompt + alertsCount={alertsCount} + isDisabled={connectorId == null} + isLoading={isLoading} + onGenerate={onGenerate} + /> + ); } - if (showNoAlertsPrompt({ alertsContextCount, connectorId, isLoading })) { - return <NoAlerts isLoading={isLoading} isDisabled={isDisabled} onGenerate={onGenerate} />; - } - - return ( - <EmptyPrompt - aiConnectorsCount={aiConnectorsCount} - alertsCount={upToAlertsCount} - attackDiscoveriesCount={attackDiscoveriesCount} - isDisabled={isDisabled} - isLoading={isLoading} - onGenerate={onGenerate} - /> - ); + return null; }; +EmptyStatesComponent.displayName = 'EmptyStates'; + export const EmptyStates = React.memo(EmptyStatesComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx index c9c27446fe51c..4318f3f78536a 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx @@ -5,53 +5,13 @@ * 2.0. */ -import { - EuiAccordion, - EuiCodeBlock, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiText, -} from '@elastic/eui'; +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; -import React, { useMemo } from 'react'; +import React from 'react'; import * as i18n from './translations'; -interface Props { - failureReason: string | null | undefined; -} - -const FailureComponent: React.FC<Props> = ({ failureReason }) => { - const Failures = useMemo(() => { - const failures = failureReason != null ? failureReason.split('\n') : ''; - const [firstFailure, ...restFailures] = failures; - - return ( - <> - <p>{firstFailure}</p> - - {restFailures.length > 0 && ( - <EuiAccordion - id="failuresFccordion" - buttonContent={i18n.DETAILS} - data-test-subj="failuresAccordion" - paddingSize="s" - > - <> - {restFailures.map((failure, i) => ( - <EuiCodeBlock fontSize="m" key={i} paddingSize="m"> - {failure} - </EuiCodeBlock> - ))} - </> - </EuiAccordion> - )} - </> - ); - }, [failureReason]); - +const FailureComponent: React.FC<{ failureReason: string }> = ({ failureReason }) => { return ( <EuiFlexGroup alignItems="center" data-test-subj="failure" direction="column"> <EuiFlexItem data-test-subj="emptyPromptContainer" grow={false}> @@ -66,7 +26,7 @@ const FailureComponent: React.FC<Props> = ({ failureReason }) => { `} data-test-subj="bodyText" > - {Failures} + {failureReason} </EuiText> } title={<h2 data-test-subj="failureTitle">{i18n.FAILURE_TITLE}</h2>} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts index ecaa7fad240e1..b36104d202ba8 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; -export const DETAILS = i18n.translate( - 'xpack.securitySolution.attackDiscovery.pages.failure.detailsAccordionButton', +export const LEARN_MORE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.pages.failure.learnMoreLink', { - defaultMessage: 'Details', + defaultMessage: 'Learn more about Attack discovery', } ); @@ -20,10 +20,3 @@ export const FAILURE_TITLE = i18n.translate( defaultMessage: 'Attack discovery generation failed', } ); - -export const LEARN_MORE = i18n.translate( - 'xpack.securitySolution.attackDiscovery.pages.failure.learnMoreLink', - { - defaultMessage: 'Learn more about Attack discovery', - } -); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx deleted file mode 100644 index 16ed376dd3af4..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButton, EuiToolTip } from '@elastic/eui'; -import React from 'react'; - -import * as i18n from '../empty_prompt/translations'; - -interface Props { - isDisabled?: boolean; - isLoading: boolean; - onGenerate: () => void; -} - -const GenerateComponent: React.FC<Props> = ({ isLoading, isDisabled = false, onGenerate }) => { - const disabled = isLoading || isDisabled; - - return ( - <EuiToolTip - content={disabled ? i18n.SELECT_A_CONNECTOR : null} - data-test-subj="generateTooltip" - > - <EuiButton color="primary" data-test-subj="generate" disabled={disabled} onClick={onGenerate}> - {i18n.GENERATE} - </EuiButton> - </EuiToolTip> - ); -}; - -GenerateComponent.displayName = 'Generate'; - -export const Generate = React.memo(GenerateComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx index 7b0688eadafef..aee53d889c7ac 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; @@ -32,11 +31,9 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} - localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onGenerate={jest.fn()} onConnectorIdSelected={jest.fn()} - setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -57,11 +54,9 @@ describe('Header', () => { connectorsAreConfigured={connectorsAreConfigured} isDisabledActions={false} isLoading={false} - localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onGenerate={jest.fn()} onConnectorIdSelected={jest.fn()} - setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -82,11 +77,9 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} - localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={onGenerate} - setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -109,11 +102,9 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={isLoading} - localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} - setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -135,11 +126,9 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={isLoading} - localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={onCancel} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} - setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -161,11 +150,9 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} - localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} - setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx index ff170805670a6..583bcc25d0eb6 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx @@ -9,11 +9,10 @@ import type { EuiButtonProps } from '@elastic/eui'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { ConnectorSelectorInline } from '@kbn/elastic-assistant'; -import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { SettingsModal } from './settings_modal'; +import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; import { StatusBell } from './status_bell'; import * as i18n from './translations'; @@ -22,11 +21,9 @@ interface Props { connectorsAreConfigured: boolean; isLoading: boolean; isDisabledActions: boolean; - localStorageAttackDiscoveryMaxAlerts: string | undefined; onGenerate: () => void; onCancel: () => void; onConnectorIdSelected: (connectorId: string) => void; - setLocalStorageAttackDiscoveryMaxAlerts: React.Dispatch<React.SetStateAction<string | undefined>>; stats: AttackDiscoveryStats | null; } @@ -35,11 +32,9 @@ const HeaderComponent: React.FC<Props> = ({ connectorsAreConfigured, isLoading, isDisabledActions, - localStorageAttackDiscoveryMaxAlerts, onGenerate, onConnectorIdSelected, onCancel, - setLocalStorageAttackDiscoveryMaxAlerts, stats, }) => { const { euiTheme } = useEuiTheme(); @@ -73,7 +68,6 @@ const HeaderComponent: React.FC<Props> = ({ }, [isLoading, handleCancel, onGenerate] ); - return ( <EuiFlexGroup alignItems="center" @@ -84,14 +78,6 @@ const HeaderComponent: React.FC<Props> = ({ data-test-subj="header" gutterSize="none" > - <EuiFlexItem grow={false}> - <SettingsModal - connectorId={connectorId} - isLoading={isLoading} - localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} - setLocalStorageAttackDiscoveryMaxAlerts={setLocalStorageAttackDiscoveryMaxAlerts} - /> - </EuiFlexItem> <StatusBell stats={stats} /> {connectorsAreConfigured && ( <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx deleted file mode 100644 index b51a1fc3f85c8..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SingleRangeChangeEvent } from '@kbn/elastic-assistant'; -import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; -import { - AlertsRange, - SELECT_FEWER_ALERTS, - YOUR_ANONYMIZATION_SETTINGS, -} from '@kbn/elastic-assistant'; -import React, { useCallback } from 'react'; - -import * as i18n from '../translations'; - -export const MAX_ALERTS = 500; -export const MIN_ALERTS = 50; -export const ROW_MIN_WITH = 550; // px -export const STEP = 50; - -interface Props { - maxAlerts: string; - setMaxAlerts: React.Dispatch<React.SetStateAction<string>>; -} - -const AlertsSettingsComponent: React.FC<Props> = ({ maxAlerts, setMaxAlerts }) => { - const onChangeAlertsRange = useCallback( - (e: SingleRangeChangeEvent) => { - setMaxAlerts(e.currentTarget.value); - }, - [setMaxAlerts] - ); - - return ( - <EuiForm component="form"> - <EuiFormRow hasChildLabel={false} label={i18n.ALERTS}> - <EuiFlexGroup direction="column" gutterSize="none"> - <EuiFlexItem grow={false}> - <AlertsRange - maxAlerts={MAX_ALERTS} - minAlerts={MIN_ALERTS} - onChange={onChangeAlertsRange} - step={STEP} - value={maxAlerts} - /> - <EuiSpacer size="m" /> - </EuiFlexItem> - - <EuiFlexItem grow={true}> - <EuiText color="subdued" size="xs"> - <span>{i18n.LATEST_AND_RISKIEST_OPEN_ALERTS(Number(maxAlerts))}</span> - </EuiText> - </EuiFlexItem> - - <EuiFlexItem grow={true}> - <EuiText color="subdued" size="xs"> - <span>{YOUR_ANONYMIZATION_SETTINGS}</span> - </EuiText> - </EuiFlexItem> - - <EuiFlexItem grow={true}> - <EuiText color="subdued" size="xs"> - <span>{SELECT_FEWER_ALERTS}</span> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFormRow> - </EuiForm> - ); -}; - -AlertsSettingsComponent.displayName = 'AlertsSettings'; - -export const AlertsSettings = React.memo(AlertsSettingsComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx deleted file mode 100644 index 0066376a0e198..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; -import React from 'react'; - -import * as i18n from '../translations'; - -interface Props { - closeModal: () => void; - onReset: () => void; - onSave: () => void; -} - -const FooterComponent: React.FC<Props> = ({ closeModal, onReset, onSave }) => { - const { euiTheme } = useEuiTheme(); - - return ( - <EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiButtonEmpty data-test-sub="reset" flush="both" onClick={onReset} size="s"> - {i18n.RESET} - </EuiButtonEmpty> - </EuiFlexItem> - - <EuiFlexItem grow={false}> - <EuiFlexGroup alignItems="center" gutterSize="none"> - <EuiFlexItem - css={css` - margin-right: ${euiTheme.size.s}; - `} - grow={false} - > - <EuiButtonEmpty data-test-sub="cancel" onClick={closeModal} size="s"> - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - - <EuiFlexItem grow={false}> - <EuiButton data-test-sub="save" fill onClick={onSave} size="s"> - {i18n.SAVE} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - ); -}; - -FooterComponent.displayName = 'Footer'; - -export const Footer = React.memo(FooterComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx deleted file mode 100644 index 0d342c591f32b..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiButtonIcon, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiText, - EuiToolTip, - EuiTourStep, - useGeneratedHtmlId, -} from '@elastic/eui'; -import { - ATTACK_DISCOVERY_STORAGE_KEY, - DEFAULT_ASSISTANT_NAMESPACE, - DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, - SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY, -} from '@kbn/elastic-assistant'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useLocalStorage } from 'react-use'; - -import { AlertsSettings } from './alerts_settings'; -import { useSpaceId } from '../../../../common/hooks/use_space_id'; -import { Footer } from './footer'; -import { getIsTourEnabled } from './is_tour_enabled'; -import * as i18n from './translations'; - -interface Props { - connectorId: string | undefined; - isLoading: boolean; - localStorageAttackDiscoveryMaxAlerts: string | undefined; - setLocalStorageAttackDiscoveryMaxAlerts: React.Dispatch<React.SetStateAction<string | undefined>>; -} - -const SettingsModalComponent: React.FC<Props> = ({ - connectorId, - isLoading, - localStorageAttackDiscoveryMaxAlerts, - setLocalStorageAttackDiscoveryMaxAlerts, -}) => { - const spaceId = useSpaceId() ?? 'default'; - const modalTitleId = useGeneratedHtmlId(); - - const [maxAlerts, setMaxAlerts] = useState( - localStorageAttackDiscoveryMaxAlerts ?? `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}` - ); - - const [isModalVisible, setIsModalVisible] = useState(false); - const showModal = useCallback(() => { - setMaxAlerts(localStorageAttackDiscoveryMaxAlerts ?? `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`); - - setIsModalVisible(true); - }, [localStorageAttackDiscoveryMaxAlerts]); - const closeModal = useCallback(() => setIsModalVisible(false), []); - - const onReset = useCallback(() => setMaxAlerts(`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`), []); - - const onSave = useCallback(() => { - setLocalStorageAttackDiscoveryMaxAlerts(maxAlerts); - closeModal(); - }, [closeModal, maxAlerts, setLocalStorageAttackDiscoveryMaxAlerts]); - - const [showSettingsTour, setShowSettingsTour] = useLocalStorage<boolean>( - `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY}.v8.16`, - true - ); - const onTourFinished = useCallback(() => setShowSettingsTour(() => false), [setShowSettingsTour]); - const [tourDelayElapsed, setTourDelayElapsed] = useState(false); - - useEffect(() => { - // visible EuiTourStep anchors don't follow the button when the layout changes (i.e. when the connectors finish loading) - const timeout = setTimeout(() => setTourDelayElapsed(true), 10000); - return () => clearTimeout(timeout); - }, []); - - const onSettingsClicked = useCallback(() => { - showModal(); - setShowSettingsTour(() => false); - }, [setShowSettingsTour, showModal]); - - const SettingsButton = useMemo( - () => ( - <EuiToolTip content={i18n.SETTINGS}> - <EuiButtonIcon - aria-label={i18n.SETTINGS} - data-test-subj="settings" - iconType="gear" - onClick={onSettingsClicked} - /> - </EuiToolTip> - ), - [onSettingsClicked] - ); - - const isTourEnabled = getIsTourEnabled({ - connectorId, - isLoading, - tourDelayElapsed, - showSettingsTour, - }); - - return ( - <> - {isTourEnabled ? ( - <EuiTourStep - anchorPosition="downCenter" - content={ - <> - <EuiText size="s"> - <p> - <span>{i18n.ATTACK_DISCOVERY_SENDS_MORE_ALERTS}</span> - <br /> - <span>{i18n.CONFIGURE_YOUR_SETTINGS_HERE}</span> - </p> - </EuiText> - </> - } - isStepOpen={showSettingsTour} - minWidth={300} - onFinish={onTourFinished} - step={1} - stepsTotal={1} - subtitle={i18n.RECENT_ATTACK_DISCOVERY_IMPROVEMENTS} - title={i18n.SEND_MORE_ALERTS} - > - {SettingsButton} - </EuiTourStep> - ) : ( - <>{SettingsButton}</> - )} - - {isModalVisible && ( - <EuiModal aria-labelledby={modalTitleId} data-test-subj="modal" onClose={closeModal}> - <EuiModalHeader> - <EuiModalHeaderTitle id={modalTitleId}>{i18n.SETTINGS}</EuiModalHeaderTitle> - </EuiModalHeader> - - <EuiModalBody> - <AlertsSettings maxAlerts={maxAlerts} setMaxAlerts={setMaxAlerts} /> - </EuiModalBody> - - <EuiModalFooter> - <Footer closeModal={closeModal} onReset={onReset} onSave={onSave} /> - </EuiModalFooter> - </EuiModal> - )} - </> - ); -}; - -SettingsModalComponent.displayName = 'SettingsModal'; - -export const SettingsModal = React.memo(SettingsModalComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts deleted file mode 100644 index 7f2f356114902..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getIsTourEnabled = ({ - connectorId, - isLoading, - tourDelayElapsed, - showSettingsTour, -}: { - connectorId: string | undefined; - isLoading: boolean; - tourDelayElapsed: boolean; - showSettingsTour: boolean | undefined; -}): boolean => !isLoading && connectorId != null && tourDelayElapsed && !!showSettingsTour; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts deleted file mode 100644 index dc42db84f2d8a..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const ALERTS = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.alertsLabel', - { - defaultMessage: 'Alerts', - } -); - -export const ATTACK_DISCOVERY_SENDS_MORE_ALERTS = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.attackDiscoverySendsMoreAlertsTourText', - { - defaultMessage: 'Attack discovery sends more alerts as context.', - } -); - -export const CANCEL = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.cancelButton', - { - defaultMessage: 'Cancel', - } -); - -export const CONFIGURE_YOUR_SETTINGS_HERE = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.configureYourSettingsHereTourText', - { - defaultMessage: 'Configure your settings here.', - } -); - -export const LATEST_AND_RISKIEST_OPEN_ALERTS = (alertsCount: number) => - i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.latestAndRiskiestOpenAlertsLabel', - { - defaultMessage: - 'Send Attack discovery information about your {alertsCount} newest and riskiest open or acknowledged alerts.', - values: { alertsCount }, - } - ); - -export const SAVE = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.saveButton', - { - defaultMessage: 'Save', - } -); - -export const SEND_MORE_ALERTS = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.tourTitle', - { - defaultMessage: 'Send more alerts', - } -); - -export const SETTINGS = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.settingsLabel', - { - defaultMessage: 'Settings', - } -); - -export const RECENT_ATTACK_DISCOVERY_IMPROVEMENTS = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.tourSubtitle', - { - defaultMessage: 'Recent Attack discovery improvements', - } -); - -export const RESET = i18n.translate( - 'xpack.securitySolution.attackDiscovery.settingsModal.resetLabel', - { - defaultMessage: 'Reset', - } -); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts index c7e1c579418b4..e94687611ea8f 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts @@ -12,7 +12,6 @@ describe('helpers', () => { it('returns true when isLoading is false and alertsContextCount is 0', () => { const result = showNoAlertsPrompt({ alertsContextCount: 0, - connectorId: 'test', isLoading: false, }); @@ -22,7 +21,6 @@ describe('helpers', () => { it('returns false when isLoading is true', () => { const result = showNoAlertsPrompt({ alertsContextCount: 0, - connectorId: 'test', isLoading: true, }); @@ -32,7 +30,6 @@ describe('helpers', () => { it('returns false when alertsContextCount is null', () => { const result = showNoAlertsPrompt({ alertsContextCount: null, - connectorId: 'test', isLoading: false, }); @@ -42,7 +39,6 @@ describe('helpers', () => { it('returns false when alertsContextCount greater than 0', () => { const result = showNoAlertsPrompt({ alertsContextCount: 20, - connectorId: 'test', isLoading: false, }); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts index b990c3ccf1555..e3d3be963bacd 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts @@ -75,14 +75,11 @@ export const getErrorToastText = ( export const showNoAlertsPrompt = ({ alertsContextCount, - connectorId, isLoading, }: { alertsContextCount: number | null; - connectorId: string | undefined; isLoading: boolean; -}): boolean => - connectorId != null && !isLoading && alertsContextCount != null && alertsContextCount === 0; +}): boolean => !isLoading && alertsContextCount != null && alertsContextCount === 0; export const showWelcomePrompt = ({ aiConnectorsCount, @@ -114,26 +111,12 @@ export const showLoading = ({ loadingConnectorId: string | null; }): boolean => isLoading && (loadingConnectorId === connectorId || attackDiscoveriesCount === 0); -export const showSummary = (attackDiscoveriesCount: number) => attackDiscoveriesCount > 0; - -export const showFailurePrompt = ({ +export const showSummary = ({ connectorId, - failureReason, - isLoading, + attackDiscoveriesCount, + loadingConnectorId, }: { connectorId: string | undefined; - failureReason: string | null; - isLoading: boolean; -}): boolean => connectorId != null && !isLoading && failureReason != null; - -export const getSize = ({ - defaultMaxAlerts, - localStorageAttackDiscoveryMaxAlerts, -}: { - defaultMaxAlerts: number; - localStorageAttackDiscoveryMaxAlerts: string | undefined; -}): number => { - const size = Number(localStorageAttackDiscoveryMaxAlerts); - - return isNaN(size) || size <= 0 ? defaultMaxAlerts : size; -}; + attackDiscoveriesCount: number; + loadingConnectorId: string | null; +}): boolean => loadingConnectorId !== connectorId && attackDiscoveriesCount > 0; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx index e55b2fe5083b6..ea5c16fc3cbba 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx @@ -5,13 +5,11 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; import { css } from '@emotion/react'; import { ATTACK_DISCOVERY_STORAGE_KEY, DEFAULT_ASSISTANT_NAMESPACE, - DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, - MAX_ALERTS_LOCAL_STORAGE_KEY, useAssistantContext, useLoadConnectors, } from '@kbn/elastic-assistant'; @@ -25,16 +23,23 @@ import { HeaderPage } from '../../common/components/header_page'; import { useSpaceId } from '../../common/hooks/use_space_id'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { Header } from './header'; -import { CONNECTOR_ID_LOCAL_STORAGE_KEY, getSize, showLoading } from './helpers'; +import { + CONNECTOR_ID_LOCAL_STORAGE_KEY, + getInitialIsOpen, + showLoading, + showSummary, +} from './helpers'; +import { AttackDiscoveryPanel } from '../attack_discovery_panel'; +import { EmptyStates } from './empty_states'; import { LoadingCallout } from './loading_callout'; import { PageTitle } from './page_title'; -import { Results } from './results'; +import { Summary } from './summary'; import { useAttackDiscovery } from '../use_attack_discovery'; const AttackDiscoveryPageComponent: React.FC = () => { const spaceId = useSpaceId() ?? 'default'; - const { http } = useAssistantContext(); + const { http, knowledgeBase } = useAssistantContext(); const { data: aiConnectors } = useLoadConnectors({ http, }); @@ -49,12 +54,6 @@ const AttackDiscoveryPageComponent: React.FC = () => { `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${CONNECTOR_ID_LOCAL_STORAGE_KEY}` ); - const [localStorageAttackDiscoveryMaxAlerts, setLocalStorageAttackDiscoveryMaxAlerts] = - useLocalStorage<string>( - `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${MAX_ALERTS_LOCAL_STORAGE_KEY}`, - `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}` - ); - const [connectorId, setConnectorId] = React.useState<string | undefined>( localStorageAttackDiscoveryConnectorId ); @@ -79,10 +78,6 @@ const AttackDiscoveryPageComponent: React.FC = () => { } = useAttackDiscovery({ connectorId, setLoadingConnectorId, - size: getSize({ - defaultMaxAlerts: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, - localStorageAttackDiscoveryMaxAlerts, - }), }); // get last updated from the cached attack discoveries if it exists: @@ -164,11 +159,9 @@ const AttackDiscoveryPageComponent: React.FC = () => { isLoading={isLoading} // disable header actions before post request has completed isDisabledActions={isLoadingPost} - localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} onConnectorIdSelected={onConnectorIdSelected} onGenerate={onGenerate} onCancel={onCancel} - setLocalStorageAttackDiscoveryMaxAlerts={setLocalStorageAttackDiscoveryMaxAlerts} stats={stats} /> <EuiSpacer size="m" /> @@ -177,37 +170,68 @@ const AttackDiscoveryPageComponent: React.FC = () => { <EuiEmptyPrompt data-test-subj="animatedLogo" icon={animatedLogo} /> ) : ( <> - {showLoading({ + {showSummary({ attackDiscoveriesCount, connectorId, - isLoading: isLoading || isLoadingPost, loadingConnectorId, - }) ? ( - <LoadingCallout - alertsContextCount={alertsContextCount} - localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} - approximateFutureTime={approximateFutureTime} - connectorIntervals={connectorIntervals} - /> - ) : ( - <Results - aiConnectorsCount={aiConnectors?.length ?? null} - alertsContextCount={alertsContextCount} + }) && ( + <Summary alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} - connectorId={connectorId} - failureReason={failureReason} - isLoading={isLoading} - isLoadingPost={isLoadingPost} - localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} - onGenerate={onGenerate} + lastUpdated={selectedConnectorLastUpdated} onToggleShowAnonymized={onToggleShowAnonymized} - selectedConnectorAttackDiscoveries={selectedConnectorAttackDiscoveries} - selectedConnectorLastUpdated={selectedConnectorLastUpdated} - selectedConnectorReplacements={selectedConnectorReplacements} showAnonymized={showAnonymized} /> )} + + <> + {showLoading({ + attackDiscoveriesCount, + connectorId, + isLoading: isLoading || isLoadingPost, + loadingConnectorId, + }) ? ( + <LoadingCallout + alertsCount={knowledgeBase.latestAlerts} + approximateFutureTime={approximateFutureTime} + connectorIntervals={connectorIntervals} + /> + ) : ( + selectedConnectorAttackDiscoveries.map((attackDiscovery, i) => ( + <React.Fragment key={attackDiscovery.id}> + <AttackDiscoveryPanel + attackDiscovery={attackDiscovery} + initialIsOpen={getInitialIsOpen(i)} + showAnonymized={showAnonymized} + replacements={selectedConnectorReplacements} + /> + <EuiSpacer size="l" /> + </React.Fragment> + )) + )} + </> + <EuiFlexGroup + css={css` + max-height: 100%; + min-height: 100%; + `} + direction="column" + gutterSize="none" + > + <EuiSpacer size="xxl" /> + <EuiFlexItem grow={false}> + <EmptyStates + aiConnectorsCount={aiConnectors?.length ?? null} + alertsContextCount={alertsContextCount} + alertsCount={knowledgeBase.latestAlerts} + attackDiscoveriesCount={attackDiscoveriesCount} + failureReason={failureReason} + connectorId={connectorId} + isLoading={isLoading || isLoadingPost} + onGenerate={onGenerate} + /> + </EuiFlexItem> + </EuiFlexGroup> </> )} <SpyRoute pageName={SecurityPageName.attackDiscovery} /> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx index f755017288300..af6efafb3c1dd 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx @@ -29,10 +29,9 @@ describe('LoadingCallout', () => { ]; const defaultProps = { - alertsContextCount: 30, + alertsCount: 30, approximateFutureTime: new Date(), connectorIntervals, - localStorageAttackDiscoveryMaxAlerts: '50', }; it('renders the animated loading icon', () => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx index aee8241ec73fc..7e392e3165711 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx @@ -20,15 +20,13 @@ const BACKGROUND_COLOR_DARK = '#0B2030'; const BORDER_COLOR_DARK = '#0B2030'; interface Props { - alertsContextCount: number | null; + alertsCount: number; approximateFutureTime: Date | null; connectorIntervals: GenerationInterval[]; - localStorageAttackDiscoveryMaxAlerts: string | undefined; } const LoadingCalloutComponent: React.FC<Props> = ({ - alertsContextCount, - localStorageAttackDiscoveryMaxAlerts, + alertsCount, approximateFutureTime, connectorIntervals, }) => { @@ -48,14 +46,11 @@ const LoadingCalloutComponent: React.FC<Props> = ({ `} grow={false} > - <LoadingMessages - alertsContextCount={alertsContextCount} - localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} - /> + <LoadingMessages alertsCount={alertsCount} /> </EuiFlexItem> </EuiFlexGroup> ), - [alertsContextCount, euiTheme.size.m, localStorageAttackDiscoveryMaxAlerts] + [alertsCount, euiTheme.size.m] ); const isDarkMode = theme.getTheme().darkMode === true; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts deleted file mode 100644 index 9a3061272ca15..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getLoadingCalloutAlertsCount = ({ - alertsContextCount, - defaultMaxAlerts, - localStorageAttackDiscoveryMaxAlerts, -}: { - alertsContextCount: number | null; - defaultMaxAlerts: number; - localStorageAttackDiscoveryMaxAlerts: string | undefined; -}): number => { - if (alertsContextCount != null && !isNaN(alertsContextCount) && alertsContextCount > 0) { - return alertsContextCount; - } - - const size = Number(localStorageAttackDiscoveryMaxAlerts); - - return isNaN(size) || size <= 0 ? defaultMaxAlerts : size; -}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx index 8b3f174792c5e..250a25055791a 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx @@ -16,7 +16,7 @@ describe('LoadingMessages', () => { it('renders the expected loading message', () => { render( <TestProviders> - <LoadingMessages alertsContextCount={20} localStorageAttackDiscoveryMaxAlerts={'30'} /> + <LoadingMessages alertsCount={20} /> </TestProviders> ); const attackDiscoveryGenerationInProgress = screen.getByTestId( @@ -31,7 +31,7 @@ describe('LoadingMessages', () => { it('renders the loading message with the expected alerts count', () => { render( <TestProviders> - <LoadingMessages alertsContextCount={20} localStorageAttackDiscoveryMaxAlerts={'30'} /> + <LoadingMessages alertsCount={20} /> </TestProviders> ); const aiCurrentlyAnalyzing = screen.getByTestId('aisCurrentlyAnalyzing'); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx index 1a84771e5c635..9acd7b4d2dbbf 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx @@ -7,34 +7,22 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; -import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import React from 'react'; import { useKibana } from '../../../../common/lib/kibana'; -import { getLoadingCalloutAlertsCount } from './get_loading_callout_alerts_count'; import * as i18n from '../translations'; const TEXT_COLOR = '#343741'; interface Props { - alertsContextCount: number | null; - localStorageAttackDiscoveryMaxAlerts: string | undefined; + alertsCount: number; } -const LoadingMessagesComponent: React.FC<Props> = ({ - alertsContextCount, - localStorageAttackDiscoveryMaxAlerts, -}) => { +const LoadingMessagesComponent: React.FC<Props> = ({ alertsCount }) => { const { theme } = useKibana().services; const isDarkMode = theme.getTheme().darkMode === true; - const alertsCount = getLoadingCalloutAlertsCount({ - alertsContextCount, - defaultMaxAlerts: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, - localStorageAttackDiscoveryMaxAlerts, - }); - return ( <EuiFlexGroup data-test-subj="loadingMessages" direction="column" gutterSize="none"> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx index 6c6bbfb25cb7f..6c2640623e370 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx @@ -13,7 +13,7 @@ import { ATTACK_DISCOVERY_ONLY, LEARN_MORE, NO_ALERTS_TO_ANALYZE } from './trans describe('NoAlerts', () => { beforeEach(() => { - render(<NoAlerts isDisabled={false} isLoading={false} onGenerate={jest.fn()} />); + render(<NoAlerts />); }); it('renders the avatar', () => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx index ace75f568bf3d..a7b0cd929336b 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx @@ -17,15 +17,8 @@ import { import React, { useMemo } from 'react'; import * as i18n from './translations'; -import { Generate } from '../generate'; -interface Props { - isDisabled: boolean; - isLoading: boolean; - onGenerate: () => void; -} - -const NoAlertsComponent: React.FC<Props> = ({ isDisabled, isLoading, onGenerate }) => { +const NoAlertsComponent: React.FC = () => { const title = useMemo( () => ( <EuiFlexGroup @@ -90,14 +83,6 @@ const NoAlertsComponent: React.FC<Props> = ({ isDisabled, isLoading, onGenerate {i18n.LEARN_MORE} </EuiLink> </EuiFlexItem> - - <EuiFlexItem grow={false}> - <EuiSpacer size="m" /> - </EuiFlexItem> - - <EuiFlexItem grow={false}> - <Generate isDisabled={isDisabled} isLoading={isLoading} onGenerate={onGenerate} /> - </EuiFlexItem> </EuiFlexGroup> ); }; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx deleted file mode 100644 index 6e3e43127e711..0000000000000 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiSpacer } from '@elastic/eui'; -import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; -import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; -import React from 'react'; - -import { AttackDiscoveryPanel } from '../../attack_discovery_panel'; -import { EmptyStates } from '../empty_states'; -import { showEmptyStates } from '../empty_states/helpers/show_empty_states'; -import { getInitialIsOpen, showSummary } from '../helpers'; -import { Summary } from '../summary'; - -interface Props { - aiConnectorsCount: number | null; // null when connectors are not configured - alertsContextCount: number | null; // null when unavailable for the current connector - alertsCount: number; - attackDiscoveriesCount: number; - connectorId: string | undefined; - failureReason: string | null; - isLoading: boolean; - isLoadingPost: boolean; - localStorageAttackDiscoveryMaxAlerts: string | undefined; - onGenerate: () => Promise<void>; - onToggleShowAnonymized: () => void; - selectedConnectorAttackDiscoveries: AttackDiscovery[]; - selectedConnectorLastUpdated: Date | null; - selectedConnectorReplacements: Replacements; - showAnonymized: boolean; -} - -const ResultsComponent: React.FC<Props> = ({ - aiConnectorsCount, - alertsContextCount, - alertsCount, - attackDiscoveriesCount, - connectorId, - failureReason, - isLoading, - isLoadingPost, - localStorageAttackDiscoveryMaxAlerts, - onGenerate, - onToggleShowAnonymized, - selectedConnectorAttackDiscoveries, - selectedConnectorLastUpdated, - selectedConnectorReplacements, - showAnonymized, -}) => { - if ( - showEmptyStates({ - aiConnectorsCount, - alertsContextCount, - attackDiscoveriesCount, - connectorId, - failureReason, - isLoading, - }) - ) { - return ( - <> - <EuiSpacer size="xxl" /> - <EmptyStates - aiConnectorsCount={aiConnectorsCount} - alertsContextCount={alertsContextCount} - attackDiscoveriesCount={attackDiscoveriesCount} - failureReason={failureReason} - connectorId={connectorId} - isLoading={isLoading || isLoadingPost} - onGenerate={onGenerate} - upToAlertsCount={Number( - localStorageAttackDiscoveryMaxAlerts ?? DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS - )} - /> - </> - ); - } - - return ( - <> - {showSummary(attackDiscoveriesCount) && ( - <Summary - alertsCount={alertsCount} - attackDiscoveriesCount={attackDiscoveriesCount} - lastUpdated={selectedConnectorLastUpdated} - onToggleShowAnonymized={onToggleShowAnonymized} - showAnonymized={showAnonymized} - /> - )} - - {selectedConnectorAttackDiscoveries.map((attackDiscovery, i) => ( - <React.Fragment key={attackDiscovery.id}> - <AttackDiscoveryPanel - attackDiscovery={attackDiscovery} - initialIsOpen={getInitialIsOpen(i)} - showAnonymized={showAnonymized} - replacements={selectedConnectorReplacements} - /> - <EuiSpacer size="l" /> - </React.Fragment> - ))} - </> - ); -}; - -ResultsComponent.displayName = 'Results'; - -export const Results = React.memo(ResultsComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts index cc0034c90d1fa..f2fd17d5978b7 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { omit } from 'lodash/fp'; @@ -133,7 +132,9 @@ describe('getRequestBody', () => { }, ], }; - + const knowledgeBase = { + latestAlerts: 20, + }; const traceOptions = { apmUrl: '/app/apm', langSmithProject: '', @@ -144,7 +145,7 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern, anonymizationFields, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + knowledgeBase, traceOptions, }); @@ -159,8 +160,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, + size: knowledgeBase.latestAlerts, replacements: {}, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); @@ -169,7 +170,7 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern: undefined, anonymizationFields, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + knowledgeBase, traceOptions, }); @@ -184,8 +185,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, + size: knowledgeBase.latestAlerts, replacements: {}, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); @@ -194,7 +195,7 @@ describe('getRequestBody', () => { const withLangSmith = { alertsIndexPattern, anonymizationFields, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + knowledgeBase, traceOptions: { apmUrl: '/app/apm', langSmithProject: 'A project', @@ -215,7 +216,7 @@ describe('getRequestBody', () => { }, langSmithApiKey: withLangSmith.traceOptions.langSmithApiKey, langSmithProject: withLangSmith.traceOptions.langSmithProject, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + size: knowledgeBase.latestAlerts, replacements: {}, subAction: 'invokeAI', }); @@ -225,8 +226,8 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern, anonymizationFields, + knowledgeBase, selectedConnector: connector, // <-- selectedConnector is provided - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -241,7 +242,7 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + size: knowledgeBase.latestAlerts, replacements: {}, subAction: 'invokeAI', }); @@ -257,8 +258,8 @@ describe('getRequestBody', () => { alertsIndexPattern, anonymizationFields, genAiConfig, // <-- genAiConfig is provided + knowledgeBase, selectedConnector: connector, // <-- selectedConnector is provided - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -273,8 +274,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, + size: knowledgeBase.latestAlerts, replacements: {}, - size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts index 7aa9bfdd118d9..97eb132bdaaeb 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts @@ -5,7 +5,10 @@ * 2.0. */ -import type { TraceOptions } from '@kbn/elastic-assistant/impl/assistant/types'; +import type { + KnowledgeBaseConfig, + TraceOptions, +} from '@kbn/elastic-assistant/impl/assistant/types'; import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import type { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; @@ -57,8 +60,8 @@ export const getRequestBody = ({ alertsIndexPattern, anonymizationFields, genAiConfig, + knowledgeBase, selectedConnector, - size, traceOptions, }: { alertsIndexPattern: string | undefined; @@ -80,7 +83,7 @@ export const getRequestBody = ({ }>; }; genAiConfig?: GenAiConfig; - size: number; + knowledgeBase: KnowledgeBaseConfig; selectedConnector?: ActionConnector; traceOptions: TraceOptions; }): AttackDiscoveryPostRequestBody => ({ @@ -92,8 +95,8 @@ export const getRequestBody = ({ langSmithApiKey: isEmpty(traceOptions?.langSmithApiKey) ? undefined : traceOptions?.langSmithApiKey, + size: knowledgeBase.latestAlerts, replacements: {}, // no need to re-use replacements in the current implementation - size, subAction: 'invokeAI', // non-streaming apiConfig: { connectorId: selectedConnector?.id ?? '', diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx index 59659ee6d8649..6329ce5ca699a 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx @@ -106,8 +106,6 @@ const mockAttackDiscoveries = [ const setLoadingConnectorId = jest.fn(); const setStatus = jest.fn(); -const SIZE = 20; - describe('useAttackDiscovery', () => { const mockPollApi = { cancelAttackDiscovery: jest.fn(), @@ -128,11 +126,7 @@ describe('useAttackDiscovery', () => { it('initializes with correct default values', () => { const { result } = renderHook(() => - useAttackDiscovery({ - connectorId: 'test-id', - setLoadingConnectorId, - size: 20, - }) + useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId }) ); expect(result.current.alertsContextCount).toBeNull(); @@ -150,15 +144,14 @@ describe('useAttackDiscovery', () => { it('fetches attack discoveries and updates state correctly', async () => { (mockedUseKibana.services.http.fetch as jest.Mock).mockResolvedValue(mockAttackDiscoveryPost); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); - + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); await act(async () => { await result.current.fetchAttackDiscoveries(); }); expect(mockedUseKibana.services.http.fetch).toHaveBeenCalledWith( '/internal/elastic_assistant/attack_discovery', { - body: `{"alertsIndexPattern":"alerts-index-pattern","anonymizationFields":[],"replacements":{},"size":${SIZE},"subAction":"invokeAI","apiConfig":{"connectorId":"test-id","actionTypeId":".gen-ai"}}`, + body: '{"alertsIndexPattern":"alerts-index-pattern","anonymizationFields":[],"size":20,"replacements":{},"subAction":"invokeAI","apiConfig":{"connectorId":"test-id","actionTypeId":".gen-ai"}}', method: 'POST', version: '1', } @@ -174,7 +167,7 @@ describe('useAttackDiscovery', () => { const error = new Error(errorMessage); (mockedUseKibana.services.http.fetch as jest.Mock).mockRejectedValue(error); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); await act(async () => { await result.current.fetchAttackDiscoveries(); @@ -191,11 +184,7 @@ describe('useAttackDiscovery', () => { it('sets loading state based on poll status', async () => { (usePollApi as jest.Mock).mockReturnValue({ ...mockPollApi, status: 'running' }); const { result } = renderHook(() => - useAttackDiscovery({ - connectorId: 'test-id', - setLoadingConnectorId, - size: SIZE, - }) + useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId }) ); expect(result.current.isLoading).toBe(true); @@ -213,7 +202,7 @@ describe('useAttackDiscovery', () => { }, status: 'succeeded', }); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); expect(result.current.alertsContextCount).toEqual(20); // this is set from usePollApi @@ -238,7 +227,7 @@ describe('useAttackDiscovery', () => { }, status: 'failed', }); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); expect(result.current.failureReason).toEqual('something bad'); expect(result.current.isLoading).toBe(false); @@ -252,13 +241,7 @@ describe('useAttackDiscovery', () => { data: [], // <-- zero connectors configured }); - renderHook(() => - useAttackDiscovery({ - connectorId: 'test-id', - setLoadingConnectorId, - size: SIZE, - }) - ); + renderHook(() => useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId })); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx index 4ad78981d4540..deb1c556bdb43 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx @@ -43,11 +43,9 @@ export interface UseAttackDiscovery { export const useAttackDiscovery = ({ connectorId, - size, setLoadingConnectorId, }: { connectorId: string | undefined; - size: number; setLoadingConnectorId?: (loadingConnectorId: string | null) => void; }): UseAttackDiscovery => { // get Kibana services and connectors @@ -77,7 +75,7 @@ export const useAttackDiscovery = ({ const [isLoading, setIsLoading] = useState(false); // get alerts index pattern and allow lists from the assistant context: - const { alertsIndexPattern, traceOptions } = useAssistantContext(); + const { alertsIndexPattern, knowledgeBase, traceOptions } = useAssistantContext(); const { data: anonymizationFields } = useFetchAnonymizationFields(); @@ -97,11 +95,18 @@ export const useAttackDiscovery = ({ alertsIndexPattern, anonymizationFields, genAiConfig, - size, + knowledgeBase, selectedConnector, traceOptions, }); - }, [aiConnectors, alertsIndexPattern, anonymizationFields, connectorId, size, traceOptions]); + }, [ + aiConnectors, + alertsIndexPattern, + anonymizationFields, + connectorId, + knowledgeBase, + traceOptions, + ]); useEffect(() => { if ( @@ -135,7 +140,7 @@ export const useAttackDiscovery = ({ useEffect(() => { if (pollData !== null && pollData.connectorId === connectorId) { if (pollData.alertsContextCount != null) setAlertsContextCount(pollData.alertsContextCount); - if (pollData.attackDiscoveries.length && pollData.attackDiscoveries[0].timestamp != null) { + if (pollData.attackDiscoveries.length) { // get last updated from timestamp, not from updatedAt since this can indicate the last time the status was updated setLastUpdated(new Date(pollData.attackDiscoveries[0].timestamp)); } diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts new file mode 100644 index 0000000000000..4d06751f57d7d --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts @@ -0,0 +1,340 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { DynamicTool } from '@langchain/core/tools'; + +import { loggerMock } from '@kbn/logging-mocks'; + +import { ATTACK_DISCOVERY_TOOL } from './attack_discovery_tool'; +import { mockAnonymizationFields } from '../mock/mock_anonymization_fields'; +import { mockEmptyOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_empty_open_and_acknowledged_alerts_qery_results'; +import { mockOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_open_and_acknowledged_alerts_query_results'; + +jest.mock('langchain/chains', () => { + const mockLLMChain = jest.fn().mockImplementation(() => ({ + call: jest.fn().mockResolvedValue({ + records: [ + { + alertIds: [ + 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', + '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', + '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', + 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', + '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', + ], + detailsMarkdown: + '- Malicious Go application named "My Go Application.app" is being executed from temporary directories, likely indicating malware delivery\n- The malicious application is spawning child processes like `osascript` to display fake system dialogs and attempt to phish user credentials ({{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }}, {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }})\n- The malicious application is also executing `chmod` to make the file `unix1` executable ({{ file.path /Users/james/unix1 }})\n- `unix1` is a potentially malicious executable that is being run with suspicious arguments related to the macOS keychain ({{ process.command_line /Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!! }})\n- Multiple detections indicate the presence of malware on the host attempting credential access and execution of malicious payloads', + entitySummaryMarkdown: + 'Malicious activity detected on {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} involving user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', + mitreAttackTactics: ['Credential Access', 'Execution'], + summaryMarkdown: + 'Multiple detections indicate the presence of malware on a macOS host {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} attempting credential theft and execution of malicious payloads targeting the user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', + title: 'Malware Delivering Malicious Payloads on macOS', + }, + ], + }), + })); + + return { + LLMChain: mockLLMChain, + }; +}); + +describe('AttackDiscoveryTool', () => { + const alertsIndexPattern = '.alerts-security.alerts-default'; + const replacements = { uuid: 'original_value' }; + const size = 20; + const request = { + body: { + actionTypeId: '.bedrock', + alertsIndexPattern, + anonymizationFields: mockAnonymizationFields, + connectorId: 'test-connector-id', + replacements, + size, + subAction: 'invokeAI', + }, + } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; + + const esClient = { + search: jest.fn(), + } as unknown as ElasticsearchClient; + const llm = jest.fn() as unknown as ActionsClientLlm; + const logger = loggerMock.create(); + + const rest = { + anonymizationFields: mockAnonymizationFields, + isEnabledKnowledgeBase: false, + llm, + logger, + onNewReplacements: jest.fn(), + size, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + (esClient.search as jest.Mock).mockResolvedValue(mockOpenAndAcknowledgedAlertsQueryResults); + }); + + describe('isSupported', () => { + it('returns false when the request is missing required anonymization parameters', () => { + const requestMissingAnonymizationParams = { + body: { + isEnabledKnowledgeBase: false, + alertsIndexPattern: '.alerts-security.alerts-default', + size: 20, + }, + } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; + + const params = { + alertsIndexPattern, + esClient, + request: requestMissingAnonymizationParams, // <-- request is missing required anonymization parameters + ...rest, + }; + + expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); + }); + + it('returns false when the alertsIndexPattern is undefined', () => { + const params = { + esClient, + request, + ...rest, + alertsIndexPattern: undefined, // <-- alertsIndexPattern is undefined + }; + + expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); + }); + + it('returns false when size is undefined', () => { + const params = { + alertsIndexPattern, + esClient, + request, + ...rest, + size: undefined, // <-- size is undefined + }; + + expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); + }); + + it('returns false when size is out of range', () => { + const params = { + alertsIndexPattern, + esClient, + request, + ...rest, + size: 0, // <-- size is out of range + }; + + expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); + }); + + it('returns false when llm is undefined', () => { + const params = { + alertsIndexPattern, + esClient, + request, + ...rest, + llm: undefined, // <-- llm is undefined + }; + + expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); + }); + + it('returns true if all required params are provided', () => { + const params = { + alertsIndexPattern, + esClient, + request, + ...rest, + }; + + expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(true); + }); + }); + + describe('getTool', () => { + it('returns null when llm is undefined', () => { + const tool = ATTACK_DISCOVERY_TOOL.getTool({ + alertsIndexPattern, + esClient, + replacements, + request, + ...rest, + llm: undefined, // <-- llm is undefined + }); + + expect(tool).toBeNull(); + }); + + it('returns a `DynamicTool` with a `func` that calls `esClient.search()` with the expected alerts query', async () => { + const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ + alertsIndexPattern, + esClient, + replacements, + request, + ...rest, + }) as DynamicTool; + + await tool.func(''); + + expect(esClient.search).toHaveBeenCalledWith({ + allow_no_indices: true, + body: { + _source: false, + fields: mockAnonymizationFields.map(({ field }) => ({ + field, + include_unmapped: true, + })), + query: { + bool: { + filter: [ + { + bool: { + filter: [ + { + bool: { + minimum_should_match: 1, + should: [ + { + match_phrase: { + 'kibana.alert.workflow_status': 'open', + }, + }, + { + match_phrase: { + 'kibana.alert.workflow_status': 'acknowledged', + }, + }, + ], + }, + }, + { + range: { + '@timestamp': { + format: 'strict_date_optional_time', + gte: 'now-24h', + lte: 'now', + }, + }, + }, + ], + must: [], + must_not: [ + { + exists: { + field: 'kibana.alert.building_block_type', + }, + }, + ], + should: [], + }, + }, + ], + }, + }, + runtime_mappings: {}, + size, + sort: [ + { + 'kibana.alert.risk_score': { + order: 'desc', + }, + }, + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }, + ignore_unavailable: true, + index: [alertsIndexPattern], + }); + }); + + it('returns a `DynamicTool` with a `func` returns an empty attack discoveries array when getAnonymizedAlerts returns no alerts', async () => { + (esClient.search as jest.Mock).mockResolvedValue( + mockEmptyOpenAndAcknowledgedAlertsQueryResults // <-- no alerts + ); + + const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ + alertsIndexPattern, + esClient, + replacements, + request, + ...rest, + }) as DynamicTool; + + const result = await tool.func(''); + const expected = JSON.stringify({ alertsContextCount: 0, attackDiscoveries: [] }, null, 2); // <-- empty attack discoveries array + + expect(result).toEqual(expected); + }); + + it('returns a `DynamicTool` with a `func` that returns the expected results', async () => { + const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ + alertsIndexPattern, + esClient, + replacements, + request, + ...rest, + }) as DynamicTool; + + await tool.func(''); + + const result = await tool.func(''); + const expected = JSON.stringify( + { + alertsContextCount: 20, + attackDiscoveries: [ + { + alertIds: [ + 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', + '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', + '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', + 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', + '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', + ], + detailsMarkdown: + '- Malicious Go application named "My Go Application.app" is being executed from temporary directories, likely indicating malware delivery\n- The malicious application is spawning child processes like `osascript` to display fake system dialogs and attempt to phish user credentials ({{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }}, {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }})\n- The malicious application is also executing `chmod` to make the file `unix1` executable ({{ file.path /Users/james/unix1 }})\n- `unix1` is a potentially malicious executable that is being run with suspicious arguments related to the macOS keychain ({{ process.command_line /Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!! }})\n- Multiple detections indicate the presence of malware on the host attempting credential access and execution of malicious payloads', + entitySummaryMarkdown: + 'Malicious activity detected on {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} involving user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', + mitreAttackTactics: ['Credential Access', 'Execution'], + summaryMarkdown: + 'Multiple detections indicate the presence of malware on a macOS host {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} attempting credential theft and execution of malicious payloads targeting the user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', + title: 'Malware Delivering Malicious Payloads on macOS', + }, + ], + }, + null, + 2 + ); + + expect(result).toEqual(expected); + }); + + it('returns a tool instance with the expected tags', () => { + const tool = ATTACK_DISCOVERY_TOOL.getTool({ + alertsIndexPattern, + esClient, + replacements, + request, + ...rest, + }) as DynamicTool; + + expect(tool.tags).toEqual(['attack-discovery']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts new file mode 100644 index 0000000000000..264862d76b8f5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PromptTemplate } from '@langchain/core/prompts'; +import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; +import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; +import { LLMChain } from 'langchain/chains'; +import { OutputFixingParser } from 'langchain/output_parsers'; +import { DynamicTool } from '@langchain/core/tools'; + +import { APP_UI_ID } from '../../../../common'; +import { getAnonymizedAlerts } from './get_anonymized_alerts'; +import { getOutputParser } from './get_output_parser'; +import { sizeIsOutOfRange } from '../open_and_acknowledged_alerts/helpers'; +import { getAttackDiscoveryPrompt } from './get_attack_discovery_prompt'; + +export interface AttackDiscoveryToolParams extends AssistantToolParams { + alertsIndexPattern: string; + size: number; +} + +export const ATTACK_DISCOVERY_TOOL_DESCRIPTION = + 'Call this for attack discoveries containing `markdown` that should be displayed verbatim (with no additional processing).'; + +/** + * Returns a tool for generating attack discoveries from open and acknowledged + * alerts, or null if the request doesn't have all the required parameters. + */ +export const ATTACK_DISCOVERY_TOOL: AssistantTool = { + id: 'attack-discovery', + name: 'AttackDiscoveryTool', + description: ATTACK_DISCOVERY_TOOL_DESCRIPTION, + sourceRegister: APP_UI_ID, + isSupported: (params: AssistantToolParams): params is AttackDiscoveryToolParams => { + const { alertsIndexPattern, llm, request, size } = params; + + return ( + requestHasRequiredAnonymizationParams(request) && + alertsIndexPattern != null && + size != null && + !sizeIsOutOfRange(size) && + llm != null + ); + }, + getTool(params: AssistantToolParams) { + if (!this.isSupported(params)) return null; + + const { + alertsIndexPattern, + anonymizationFields, + esClient, + langChainTimeout, + llm, + onNewReplacements, + replacements, + size, + } = params as AttackDiscoveryToolParams; + + return new DynamicTool({ + name: 'AttackDiscoveryTool', + description: ATTACK_DISCOVERY_TOOL_DESCRIPTION, + func: async () => { + if (llm == null) { + throw new Error('LLM is required for attack discoveries'); + } + + const anonymizedAlerts = await getAnonymizedAlerts({ + alertsIndexPattern, + anonymizationFields, + esClient, + onNewReplacements, + replacements, + size, + }); + + const alertsContextCount = anonymizedAlerts.length; + if (alertsContextCount === 0) { + // No alerts to analyze, so return an empty attack discoveries array + return JSON.stringify({ alertsContextCount, attackDiscoveries: [] }, null, 2); + } + + const outputParser = getOutputParser(); + const outputFixingParser = OutputFixingParser.fromLLM(llm, outputParser); + + const prompt = new PromptTemplate({ + template: `Answer the user's question as best you can:\n{format_instructions}\n{query}`, + inputVariables: ['query'], + partialVariables: { + format_instructions: outputFixingParser.getFormatInstructions(), + }, + }); + + const answerFormattingChain = new LLMChain({ + llm, + prompt, + outputKey: 'records', + outputParser: outputFixingParser, + }); + + const result = await answerFormattingChain.call({ + query: getAttackDiscoveryPrompt({ anonymizedAlerts }), + timeout: langChainTimeout, + }); + const attackDiscoveries = result.records; + + return JSON.stringify({ alertsContextCount, attackDiscoveries }, null, 2); + }, + tags: ['attack-discovery'], + }); + }, +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts similarity index 90% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts rename to x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts index b616c392ddd21..6b7526870eb9f 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts @@ -6,19 +6,19 @@ */ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { getOpenAndAcknowledgedAlertsQuery } from '@kbn/elastic-assistant-common'; -const MIN_SIZE = 10; +import { getAnonymizedAlerts } from './get_anonymized_alerts'; +import { mockOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_open_and_acknowledged_alerts_query_results'; +import { getOpenAndAcknowledgedAlertsQuery } from '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query'; +import { MIN_SIZE } from '../open_and_acknowledged_alerts/helpers'; -import { getAnonymizedAlerts } from '.'; -import { mockOpenAndAcknowledgedAlertsQueryResults } from '../../../../mock/mock_open_and_acknowledged_alerts_query_results'; - -jest.mock('@kbn/elastic-assistant-common', () => { - const original = jest.requireActual('@kbn/elastic-assistant-common'); +jest.mock('../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query', () => { + const original = jest.requireActual( + '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query' + ); return { - ...original, - getOpenAndAcknowledgedAlertsQuery: jest.fn(), + getOpenAndAcknowledgedAlertsQuery: jest.fn(() => original), }; }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts similarity index 77% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts rename to x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts index bc2a7f5bf9e71..5989caf439518 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts @@ -7,16 +7,12 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { ElasticsearchClient } from '@kbn/core/server'; -import { - Replacements, - getAnonymizedValue, - getOpenAndAcknowledgedAlertsQuery, - getRawDataOrDefault, - sizeIsOutOfRange, - transformRawData, -} from '@kbn/elastic-assistant-common'; +import type { Replacements } from '@kbn/elastic-assistant-common'; +import { getAnonymizedValue, transformRawData } from '@kbn/elastic-assistant-common'; +import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import { getOpenAndAcknowledgedAlertsQuery } from '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query'; +import { getRawDataOrDefault, sizeIsOutOfRange } from '../open_and_acknowledged_alerts/helpers'; export const getAnonymizedAlerts = async ({ alertsIndexPattern, diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts similarity index 70% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts rename to x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts index 287f5e6b2130a..bc290bf172382 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts @@ -4,17 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { getAttackDiscoveryPrompt } from './get_attack_discovery_prompt'; -import { getAlertsContextPrompt } from '.'; -import { getDefaultAttackDiscoveryPrompt } from '../../../helpers/get_default_attack_discovery_prompt'; - -describe('getAlertsContextPrompt', () => { - it('generates the correct prompt', () => { +describe('getAttackDiscoveryPrompt', () => { + it('should generate the correct attack discovery prompt', () => { const anonymizedAlerts = ['Alert 1', 'Alert 2', 'Alert 3']; - const expected = `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. You MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds). + const expected = `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. -Use context from the following alerts to provide insights: +Use context from the following open and acknowledged alerts to provide insights: """ Alert 1 @@ -25,10 +23,7 @@ Alert 3 """ `; - const prompt = getAlertsContextPrompt({ - anonymizedAlerts, - attackDiscoveryPrompt: getDefaultAttackDiscoveryPrompt(), - }); + const prompt = getAttackDiscoveryPrompt({ anonymizedAlerts }); expect(prompt).toEqual(expected); }); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts new file mode 100644 index 0000000000000..df211f0bd0a7d --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +// NOTE: we ask the LLM to `provide insights`. We do NOT use the feature name, `AttackDiscovery`, in the prompt. +export const getAttackDiscoveryPrompt = ({ + anonymizedAlerts, +}: { + anonymizedAlerts: string[]; +}) => `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. + +Use context from the following open and acknowledged alerts to provide insights: + +""" +${anonymizedAlerts.join('\n\n')} +""" +`; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts new file mode 100644 index 0000000000000..5ad2cd11f817a --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { getOutputParser } from './get_output_parser'; + +describe('getOutputParser', () => { + it('returns a structured output parser with the expected format instructions', () => { + const outputParser = getOutputParser(); + + const expected = `You must format your output as a JSON value that adheres to a given \"JSON Schema\" instance. + +\"JSON Schema\" is a declarative language that allows you to annotate and validate JSON documents. + +For example, the example \"JSON Schema\" instance {{\"properties\": {{\"foo\": {{\"description\": \"a list of test words\", \"type\": \"array\", \"items\": {{\"type\": \"string\"}}}}}}, \"required\": [\"foo\"]}}}} +would match an object with one required property, \"foo\". The \"type\" property specifies \"foo\" must be an \"array\", and the \"description\" property semantically describes it as \"a list of test words\". The items within \"foo\" must be strings. +Thus, the object {{\"foo\": [\"bar\", \"baz\"]}} is a well-formatted instance of this example \"JSON Schema\". The object {{\"properties\": {{\"foo\": [\"bar\", \"baz\"]}}}} is not well-formatted. + +Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas! + +Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock: +\`\`\`json +{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"alertIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The alert IDs that the insight is based on.\"},\"detailsMarkdown\":{\"type\":\"string\",\"description\":\"A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"},\"entitySummaryMarkdown\":{\"type\":\"string\",\"description\":\"A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"mitreAttackTactics\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"An array of MITRE ATT&CK tactic for the insight, using one of the following values: Reconnaissance,Initial Access,Execution,Persistence,Privilege Escalation,Discovery,Lateral Movement,Command and Control,Exfiltration\"},\"summaryMarkdown\":{\"type\":\"string\",\"description\":\"A markdown summary of insight, using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"title\":{\"type\":\"string\",\"description\":\"A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.\"}},\"required\":[\"alertIds\",\"detailsMarkdown\",\"summaryMarkdown\",\"title\"],\"additionalProperties\":false},\"description\":\"Insights with markdown that always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\",\"$schema\":\"http://json-schema.org/draft-07/schema#\"} +\`\`\` +`; + + expect(outputParser.getFormatInstructions()).toEqual(expected); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts new file mode 100644 index 0000000000000..3d66257f060e4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts @@ -0,0 +1,80 @@ +/* + * 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 { StructuredOutputParser } from 'langchain/output_parsers'; +import { z } from '@kbn/zod'; + +export const SYNTAX = '{{ field.name fieldValue1 fieldValue2 fieldValueN }}'; +const GOOD_SYNTAX_EXAMPLES = + 'Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }}'; + +const BAD_SYNTAX_EXAMPLES = + 'Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}'; + +const RECONNAISSANCE = 'Reconnaissance'; +const INITIAL_ACCESS = 'Initial Access'; +const EXECUTION = 'Execution'; +const PERSISTENCE = 'Persistence'; +const PRIVILEGE_ESCALATION = 'Privilege Escalation'; +const DISCOVERY = 'Discovery'; +const LATERAL_MOVEMENT = 'Lateral Movement'; +const COMMAND_AND_CONTROL = 'Command and Control'; +const EXFILTRATION = 'Exfiltration'; + +const MITRE_ATTACK_TACTICS = [ + RECONNAISSANCE, + INITIAL_ACCESS, + EXECUTION, + PERSISTENCE, + PRIVILEGE_ESCALATION, + DISCOVERY, + LATERAL_MOVEMENT, + COMMAND_AND_CONTROL, + EXFILTRATION, +] as const; + +// NOTE: we ask the LLM for `insight`s. We do NOT use the feature name, `AttackDiscovery`, in the prompt. +export const getOutputParser = () => + StructuredOutputParser.fromZodSchema( + z + .array( + z.object({ + alertIds: z.string().array().describe(`The alert IDs that the insight is based on.`), + detailsMarkdown: z + .string() + .describe( + `A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` + ), + entitySummaryMarkdown: z + .string() + .optional() + .describe( + `A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same ${SYNTAX} syntax` + ), + mitreAttackTactics: z + .string() + .array() + .optional() + .describe( + `An array of MITRE ATT&CK tactic for the insight, using one of the following values: ${MITRE_ATTACK_TACTICS.join( + ',' + )}` + ), + summaryMarkdown: z + .string() + .describe(`A markdown summary of insight, using the same ${SYNTAX} syntax`), + title: z + .string() + .describe( + 'A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.' + ), + }) + ) + .describe( + `Insights with markdown that always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` + ) + ); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/index.ts index 1b6e90eb7280f..a704aaa44d0a1 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/index.ts @@ -10,6 +10,7 @@ import type { AssistantTool } from '@kbn/elastic-assistant-plugin/server'; import { NL_TO_ESQL_TOOL } from './esql/nl_to_esql_tool'; import { ALERT_COUNTS_TOOL } from './alert_counts/alert_counts_tool'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool'; +import { ATTACK_DISCOVERY_TOOL } from './attack_discovery/attack_discovery_tool'; import { KNOWLEDGE_BASE_RETRIEVAL_TOOL } from './knowledge_base/knowledge_base_retrieval_tool'; import { KNOWLEDGE_BASE_WRITE_TOOL } from './knowledge_base/knowledge_base_write_tool'; import { SECURITY_LABS_KNOWLEDGE_BASE_TOOL } from './security_labs/security_labs_tool'; @@ -21,6 +22,7 @@ export const getAssistantTools = ({ }): AssistantTool[] => { const tools = [ ALERT_COUNTS_TOOL, + ATTACK_DISCOVERY_TOOL, NL_TO_ESQL_TOOL, KNOWLEDGE_BASE_RETRIEVAL_TOOL, KNOWLEDGE_BASE_WRITE_TOOL, diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields.ts b/x-pack/plugins/security_solution/server/assistant/tools/mock/mock_anonymization_fields.ts similarity index 100% rename from x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields.ts rename to x-pack/plugins/security_solution/server/assistant/tools/mock/mock_anonymization_fields.ts diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts similarity index 96% rename from x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts rename to x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts index 975896f381443..c8b52779d7b42 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getOpenAndAcknowledgedAlertsQuery } from '.'; +import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query'; describe('getOpenAndAcknowledgedAlertsQuery', () => { it('returns the expected query', () => { diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts similarity index 87% rename from x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts rename to x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts index 6f6e196053ca6..4090e71baa371 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts @@ -5,13 +5,8 @@ * 2.0. */ -import type { AnonymizationFieldResponse } from '../../schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; -/** - * This query returns open and acknowledged (non-building block) alerts in the last 24 hours. - * - * The alerts are ordered by risk score, and then from the most recent to the oldest. - */ export const getOpenAndAcknowledgedAlertsQuery = ({ alertsIndexPattern, anonymizationFields, diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts new file mode 100644 index 0000000000000..722936a368b36 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts @@ -0,0 +1,117 @@ +/* + * 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 { + getRawDataOrDefault, + isRawDataValid, + MAX_SIZE, + MIN_SIZE, + sizeIsOutOfRange, +} from './helpers'; + +describe('helpers', () => { + describe('isRawDataValid', () => { + it('returns true for valid raw data', () => { + const rawData = { + field1: [1, 2, 3], // the Fields API may return a number array + field2: ['a', 'b', 'c'], // the Fields API may return a string array + }; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns true when a field array is empty', () => { + const rawData = { + field1: [1, 2, 3], // the Fields API may return a number array + field2: ['a', 'b', 'c'], // the Fields API may return a string array + field3: [], // the Fields API may return an empty array + }; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns false when a field does not have an array of values', () => { + const rawData = { + field1: [1, 2, 3], + field2: 'invalid', + }; + + expect(isRawDataValid(rawData)).toBe(false); + }); + + it('returns true for empty raw data', () => { + const rawData = {}; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns false when raw data is an unexpected type', () => { + const rawData = 1234; + + // @ts-expect-error + expect(isRawDataValid(rawData)).toBe(false); + }); + }); + + describe('getRawDataOrDefault', () => { + it('returns the raw data when it is valid', () => { + const rawData = { + field1: [1, 2, 3], + field2: ['a', 'b', 'c'], + }; + + expect(getRawDataOrDefault(rawData)).toEqual(rawData); + }); + + it('returns an empty object when the raw data is invalid', () => { + const rawData = { + field1: [1, 2, 3], + field2: 'invalid', + }; + + expect(getRawDataOrDefault(rawData)).toEqual({}); + }); + }); + + describe('sizeIsOutOfRange', () => { + it('returns true when size is undefined', () => { + const size = undefined; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns true when size is less than MIN_SIZE', () => { + const size = MIN_SIZE - 1; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns true when size is greater than MAX_SIZE', () => { + const size = MAX_SIZE + 1; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns false when size is exactly MIN_SIZE', () => { + const size = MIN_SIZE; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + + it('returns false when size is exactly MAX_SIZE', () => { + const size = MAX_SIZE; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + + it('returns false when size is within the valid range', () => { + const size = MIN_SIZE + 1; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts new file mode 100644 index 0000000000000..dcb30e04e9dbc --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.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 type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +export const MIN_SIZE = 10; +export const MAX_SIZE = 10000; + +export type MaybeRawData = SearchResponse['fields'] | undefined; // note: this is the type of the "fields" property in the ES response + +export const isRawDataValid = (rawData: MaybeRawData): rawData is Record<string, unknown[]> => + typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x])); + +export const getRawDataOrDefault = (rawData: MaybeRawData): Record<string, unknown[]> => + isRawDataValid(rawData) ? rawData : {}; + +export const sizeIsOutOfRange = (size?: number): boolean => + size == null || size < MIN_SIZE || size > MAX_SIZE; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts index 45587b65f5f4c..09bae1639f1b1 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts @@ -10,13 +10,12 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { DynamicTool } from '@langchain/core/tools'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts_tool'; +import { MAX_SIZE } from './helpers'; import type { RetrievalQAChain } from 'langchain/chains'; import { mockAlertsFieldsApi } from '@kbn/elastic-assistant-plugin/server/__mocks__/alerts'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; import { loggerMock } from '@kbn/logging-mocks'; -const MAX_SIZE = 10000; - describe('OpenAndAcknowledgedAlertsTool', () => { const alertsIndexPattern = 'alerts-index'; const esClient = { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts index cab015183f4a2..d6b0ad58d8adb 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts @@ -7,17 +7,13 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { Replacements } from '@kbn/elastic-assistant-common'; -import { - getAnonymizedValue, - getOpenAndAcknowledgedAlertsQuery, - getRawDataOrDefault, - sizeIsOutOfRange, - transformRawData, -} from '@kbn/elastic-assistant-common'; +import { getAnonymizedValue, transformRawData } from '@kbn/elastic-assistant-common'; import { DynamicStructuredTool } from '@langchain/core/tools'; import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; import { z } from '@kbn/zod'; import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; +import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query'; +import { getRawDataOrDefault, sizeIsOutOfRange } from './helpers'; import { APP_UI_ID } from '../../../../common'; export interface OpenAndAcknowledgedAlertsToolParams extends AssistantToolParams { diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index ce79bd061548f..0d369f3c620c4 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -205,6 +205,7 @@ "@kbn/search-types", "@kbn/field-utils", "@kbn/core-saved-objects-api-server-mocks", + "@kbn/langchain", "@kbn/core-analytics-browser", "@kbn/core-i18n-browser", "@kbn/core-theme-browser", From ceea2ce6a52b1283b31c9342468570844d286f06 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita <nikita.khristinin@elastic.co> Date: Tue, 15 Oct 2024 20:59:48 +0200 Subject: [PATCH 60/84] Use internal user to create list (#196341) Recently there was changes which restrict creation of dot notation indices for not operator user in serverless. We created `.list-${space}` from the current user, by making API request from UI which is failing right now This is quick fix, which use internal user to create lists. Currently this check available only on serverless QA, but there is a plan to ship it to prod. Which will block the serverless release, as all tests failed. We checked on QA env, that with main branch we can't create those indices, but with this PR deployed, it fix it. --- x-pack/plugins/lists/server/plugin.ts | 9 +++++++- .../list_index/create_list_index_route.ts | 4 ++-- .../routes/utils/get_internal_list_client.ts | 21 +++++++++++++++++++ .../lists/server/routes/utils/index.ts | 1 + x-pack/plugins/lists/server/types.ts | 1 + .../routes/__mocks__/request_context.ts | 1 + 6 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/lists/server/routes/utils/get_internal_list_client.ts diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 5878eb45adfa5..e51be74ec21fd 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -103,7 +103,7 @@ export class ListPlugin implements Plugin<ListPluginSetup, ListsPluginStart, {}, security, savedObjects: { client: savedObjectsClient }, elasticsearch: { - client: { asCurrentUser: esClient }, + client: { asCurrentUser: esClient, asInternalUser: internalEsClient }, }, } = await context.core; if (config == null) { @@ -121,6 +121,13 @@ export class ListPlugin implements Plugin<ListPluginSetup, ListsPluginStart, {}, }), getExtensionPointClient: (): ExtensionPointStorageClientInterface => extensionPoints.getClient(), + getInternalListClient: (): ListClient => + new ListClient({ + config, + esClient: internalEsClient, + spaceId, + user, + }), getListClient: (): ListClient => new ListClient({ config, diff --git a/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts b/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts index 2f74871f23fc2..5842d7032a8bc 100644 --- a/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts @@ -11,7 +11,7 @@ import { CreateListIndexResponse } from '@kbn/securitysolution-lists-common/api' import type { ListsPluginRouter } from '../../types'; import { buildSiemResponse } from '../utils'; -import { getListClient } from '..'; +import { getInternalListClient } from '..'; export const createListIndexRoute = (router: ListsPluginRouter): void => { router.versioned @@ -26,7 +26,7 @@ export const createListIndexRoute = (router: ListsPluginRouter): void => { const siemResponse = buildSiemResponse(response); try { - const lists = await getListClient(context); + const lists = await getInternalListClient(context); const listDataStreamExists = await lists.getListDataStreamExists(); const listItemDataStreamExists = await lists.getListItemDataStreamExists(); diff --git a/x-pack/plugins/lists/server/routes/utils/get_internal_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_internal_list_client.ts new file mode 100644 index 0000000000000..8e81ad5013fe1 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_internal_list_client.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ListClient } from '../../services/lists/list_client'; +import { ErrorWithStatusCode } from '../../error_with_status_code'; +import type { ListsRequestHandlerContext } from '../../types'; + +export const getInternalListClient = async ( + context: ListsRequestHandlerContext +): Promise<ListClient> => { + const lists = (await context.lists)?.getInternalListClient(); + if (lists == null) { + throw new ErrorWithStatusCode('Lists is not found as a plugin', 404); + } else { + return lists; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/index.ts b/x-pack/plugins/lists/server/routes/utils/index.ts index f035ae5dbfe9b..03966adf3df43 100644 --- a/x-pack/plugins/lists/server/routes/utils/index.ts +++ b/x-pack/plugins/lists/server/routes/utils/index.ts @@ -8,6 +8,7 @@ export * from './get_error_message_exception_list_item'; export * from './get_error_message_exception_list'; export * from './get_list_client'; +export * from './get_internal_list_client'; export * from './get_exception_list_client'; export * from './route_validation'; export * from './build_siem_response'; diff --git a/x-pack/plugins/lists/server/types.ts b/x-pack/plugins/lists/server/types.ts index 78fdd0e8534a6..e3b277c693412 100644 --- a/x-pack/plugins/lists/server/types.ts +++ b/x-pack/plugins/lists/server/types.ts @@ -53,6 +53,7 @@ export interface ListPluginSetup { * @public */ export interface ListsApiRequestHandlerContext { + getInternalListClient: () => ListClient; getListClient: () => ListClient; getExceptionListClient: () => ExceptionListClient; getExtensionPointClient: () => ExtensionPointStorageClientInterface; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index a5e0c8c60b1fc..f562ea7f7bf5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -107,6 +107,7 @@ const createRequestContextMock = ( getListClient: jest.fn(() => clients.lists.listClient), getExceptionListClient: jest.fn(() => clients.lists.exceptionListClient), getExtensionPointClient: jest.fn(), + getInternalListClient: jest.fn(), }, }; }; From 7217b51452a089b808142ade04da8dafb01c180b Mon Sep 17 00:00:00 2001 From: Nick Peihl <nick.peihl@elastic.co> Date: Tue, 15 Oct 2024 15:05:24 -0400 Subject: [PATCH 61/84] [Canvas] Fix unescaped backslashes (#196311) Fixes unescaped backslashes in Canvas autocomplete --- .../public/components/expression_input/autocomplete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/presentation_util/public/components/expression_input/autocomplete.ts b/src/plugins/presentation_util/public/components/expression_input/autocomplete.ts index 16d0e10127403..ae317c48dd87b 100644 --- a/src/plugins/presentation_util/public/components/expression_input/autocomplete.ts +++ b/src/plugins/presentation_util/public/components/expression_input/autocomplete.ts @@ -439,7 +439,7 @@ function maybeQuote(value: any) { if (value.match(/^\{.*\}$/)) { return value; } - return `"${value.replace(/"/g, '\\"')}"`; + return `"${value.replace(/[\\"]/g, '\\$&')}"`; } return value; } From a63b93976c34a8f9bb78f0426ee52ef533d73712 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet <nicolas.chaulet@elastic.co> Date: Tue, 15 Oct 2024 15:18:43 -0400 Subject: [PATCH 62/84] [Fleet] Add placeholder and comments to integration config (#195735) --- package.json | 1 + src/dev/build/tasks/clean_tasks.ts | 1 + src/dev/yarn_deduplicate/index.ts | 2 +- .../epm/packages/__fixtures__/logs_2_3_0.ts | 136 ++++++++++ .../redis_1_18_0_package_info.json | 245 ++++++++++++++++++ .../redis_1_18_0_streams_template.ts | 81 ++++++ .../get_templates_inputs.test.ts.snap | 56 ++++ .../epm/packages/get_template_inputs.ts | 226 ++++++++++++++-- .../epm/packages/get_templates_inputs.test.ts | 57 +++- .../apis/epm/get_templates_inputs.ts | 13 +- yarn.lock | 5 + 11 files changed, 799 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/logs_2_3_0.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap diff --git a/package.json b/package.json index d8a97951b897d..0a7c0d6936d0a 100644 --- a/package.json +++ b/package.json @@ -1292,6 +1292,7 @@ "xstate": "^4.38.2", "xstate5": "npm:xstate@^5.18.1", "xterm": "^5.1.0", + "yaml": "^2.5.1", "yauzl": "^2.10.0", "yazl": "^2.5.1", "zod": "^3.22.3" diff --git a/src/dev/build/tasks/clean_tasks.ts b/src/dev/build/tasks/clean_tasks.ts index ad8eeaadaad60..19af3954fde45 100644 --- a/src/dev/build/tasks/clean_tasks.ts +++ b/src/dev/build/tasks/clean_tasks.ts @@ -56,6 +56,7 @@ export const CleanExtraFilesFromModules: Task = { // docs '**/doc', + '!**/yaml/dist/**/doc', // yaml package store code under doc https://github.com/eemeli/yaml/issues/384 '**/docs', '**/README', '**/CONTRIBUTING.md', diff --git a/src/dev/yarn_deduplicate/index.ts b/src/dev/yarn_deduplicate/index.ts index 3f942252e39ab..f95ee583fba01 100644 --- a/src/dev/yarn_deduplicate/index.ts +++ b/src/dev/yarn_deduplicate/index.ts @@ -17,7 +17,7 @@ const yarnLock = readFileSync(yarnLockFile, 'utf-8'); const output = fixDuplicates(yarnLock, { useMostCommon: false, excludeScopes: ['@types'], - excludePackages: ['axe-core', '@babel/types', 'csstype'], + excludePackages: ['axe-core', '@babel/types', 'csstype', 'yaml'], }); writeFileSync(yarnLockFile, output); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/logs_2_3_0.ts b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/logs_2_3_0.ts new file mode 100644 index 0000000000000..abbf60400271e --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/logs_2_3_0.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const LOGS_2_3_0_PACKAGE_INFO = { + name: 'log', + version: '2.3.0', + title: 'Custom Logs', + owner: { github: 'elastic/elastic-agent-data-plane' }, + type: 'input', + categories: ['custom', 'custom_logs'], + conditions: { 'kibana.version': '^8.8.0' }, + icons: [{ src: '/img/icon.svg', type: 'image/svg+xml' }], + policy_templates: [ + { + name: 'logs', + title: 'Custom log file', + description: 'Collect your custom log files.', + multiple: true, + input: 'logfile', + type: 'logs', + template_path: 'input.yml.hbs', + vars: [ + { + name: 'paths', + required: true, + title: 'Log file path', + description: 'Path to log files to be collected', + type: 'text', + multi: true, + }, + { + name: 'exclude_files', + required: false, + show_user: false, + title: 'Exclude files', + description: 'Patterns to be ignored', + type: 'text', + multi: true, + }, + { + name: 'ignore_older', + type: 'text', + title: 'Ignore events older than', + default: '72h', + required: false, + show_user: false, + description: + 'If this option is specified, events that are older than the specified amount of time are ignored. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".', + }, + { + name: 'data_stream.dataset', + required: true, + title: 'Dataset name', + description: + "Set the name for your dataset. Changing the dataset will send the data to a different index. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html).\n", + type: 'text', + }, + { + name: 'tags', + type: 'text', + title: 'Tags', + description: 'Tags to include in the published event', + multi: true, + show_user: false, + }, + { + name: 'processors', + type: 'yaml', + title: 'Processors', + multi: false, + required: false, + show_user: false, + description: + 'Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.', + }, + { + name: 'custom', + title: 'Custom configurations', + description: + 'Here YAML configuration options can be used to be added to your configuration. Be careful using this as it might break your configuration file.\n', + type: 'yaml', + default: '', + }, + ], + }, + ], + elasticsearch: {}, + description: 'Collect custom logs with Elastic Agent.', + format_version: '2.6.0', + readme: '/package/log/2.3.0/docs/README.md', + release: 'ga', + latestVersion: '2.3.2', + assets: {}, + licensePath: '/package/log/2.3.0/LICENSE.txt', + keepPoliciesUpToDate: false, + status: 'not_installed', +}; + +export const LOGS_2_3_0_ASSETS_MAP = new Map([ + [ + 'log-2.3.0/agent/input/input.yml.hbs', + Buffer.from(`paths: +{{#each paths}} + - {{this}} +{{/each}} + +{{#if exclude_files}} +exclude_files: +{{#each exclude_files}} + - {{this}} +{{/each}} +{{/if}} +{{#if ignore_older}} +ignore_older: {{ignore_older}} +{{/if}} +data_stream: + dataset: {{data_stream.dataset}} +{{#if processors.length}} +processors: +{{processors}} +{{/if}} +{{#if tags.length}} +tags: +{{#each tags as |tag i|}} +- {{tag}} +{{/each}} +{{/if}} + +{{custom}} +`), + ], +]); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json new file mode 100644 index 0000000000000..57c9b0c68fac9 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json @@ -0,0 +1,245 @@ +{ + "name": "redis", + "title": "Redis", + "version": "1.18.0", + "release": "ga", + "description": "Collect logs and metrics from Redis servers with Elastic Agent.", + "type": "integration", + "download": "/epr/redis/redis-1.18.0.zip", + "path": "/package/redis/1.18.0", + "icons": [ + { + "src": "/img/logo_redis.svg", + "path": "/package/redis/1.18.0/img/logo_redis.svg", + "title": "logo redis", + "size": "32x32", + "type": "image/svg+xml" + } + ], + "conditions": { + "kibana": { + "version": "^8.13.0" + }, + "elastic": { + "subscription": "basic" + } + }, + "owner": { + "type": "elastic", + "github": "elastic/obs-infraobs-integrations" + }, + "categories": ["datastore", "observability"], + "signature_path": "/epr/redis/redis-1.18.0.zip.sig", + "format_version": "3.0.2", + "readme": "/package/redis/1.18.0/docs/README.md", + "license": "basic", + "screenshots": [ + { + "src": "/img/kibana-redis.png", + "path": "/package/redis/1.18.0/img/kibana-redis.png", + "title": "kibana redis", + "size": "1124x1079", + "type": "image/png" + }, + { + "src": "/img/metricbeat_redis_key_dashboard.png", + "path": "/package/redis/1.18.0/img/metricbeat_redis_key_dashboard.png", + "title": "metricbeat redis key dashboard", + "size": "1855x949", + "type": "image/png" + }, + { + "src": "/img/metricbeat_redis_overview_dashboard.png", + "path": "/package/redis/1.18.0/img/metricbeat_redis_overview_dashboard.png", + "title": "metricbeat redis overview dashboard", + "size": "1855x949", + "type": "image/png" + } + ], + "assets": [ + "/package/redis/1.18.0/LICENSE.txt", + "/package/redis/1.18.0/changelog.yml", + "/package/redis/1.18.0/manifest.yml", + "/package/redis/1.18.0/docs/README.md", + "/package/redis/1.18.0/img/kibana-redis.png", + "/package/redis/1.18.0/img/logo_redis.svg", + "/package/redis/1.18.0/img/metricbeat_redis_key_dashboard.png", + "/package/redis/1.18.0/img/metricbeat_redis_overview_dashboard.png", + "/package/redis/1.18.0/data_stream/info/manifest.yml", + "/package/redis/1.18.0/data_stream/info/sample_event.json", + "/package/redis/1.18.0/data_stream/key/manifest.yml", + "/package/redis/1.18.0/data_stream/key/sample_event.json", + "/package/redis/1.18.0/data_stream/keyspace/manifest.yml", + "/package/redis/1.18.0/data_stream/keyspace/sample_event.json", + "/package/redis/1.18.0/data_stream/log/manifest.yml", + "/package/redis/1.18.0/data_stream/slowlog/manifest.yml", + "/package/redis/1.18.0/kibana/dashboard/redis-28969190-0511-11e9-9c60-d582a238e2c5.json", + "/package/redis/1.18.0/kibana/dashboard/redis-7fea2930-478e-11e7-b1f0-cb29bac6bf8b.json", + "/package/redis/1.18.0/kibana/dashboard/redis-AV4YjZ5pux-M-tCAunxK.json", + "/package/redis/1.18.0/data_stream/info/fields/agent.yml", + "/package/redis/1.18.0/data_stream/info/fields/base-fields.yml", + "/package/redis/1.18.0/data_stream/info/fields/ecs.yml", + "/package/redis/1.18.0/data_stream/info/fields/fields.yml", + "/package/redis/1.18.0/data_stream/key/fields/agent.yml", + "/package/redis/1.18.0/data_stream/key/fields/base-fields.yml", + "/package/redis/1.18.0/data_stream/key/fields/ecs.yml", + "/package/redis/1.18.0/data_stream/key/fields/fields.yml", + "/package/redis/1.18.0/data_stream/keyspace/fields/agent.yml", + "/package/redis/1.18.0/data_stream/keyspace/fields/base-fields.yml", + "/package/redis/1.18.0/data_stream/keyspace/fields/ecs.yml", + "/package/redis/1.18.0/data_stream/keyspace/fields/fields.yml", + "/package/redis/1.18.0/data_stream/log/fields/agent.yml", + "/package/redis/1.18.0/data_stream/log/fields/base-fields.yml", + "/package/redis/1.18.0/data_stream/log/fields/fields.yml", + "/package/redis/1.18.0/data_stream/slowlog/fields/agent.yml", + "/package/redis/1.18.0/data_stream/slowlog/fields/base-fields.yml", + "/package/redis/1.18.0/data_stream/slowlog/fields/fields.yml", + "/package/redis/1.18.0/data_stream/info/agent/stream/stream.yml.hbs", + "/package/redis/1.18.0/data_stream/key/agent/stream/stream.yml.hbs", + "/package/redis/1.18.0/data_stream/keyspace/agent/stream/stream.yml.hbs", + "/package/redis/1.18.0/data_stream/log/agent/stream/stream.yml.hbs", + "/package/redis/1.18.0/data_stream/log/elasticsearch/ingest_pipeline/default.yml", + "/package/redis/1.18.0/data_stream/slowlog/agent/stream/stream.yml.hbs", + "/package/redis/1.18.0/data_stream/slowlog/elasticsearch/ingest_pipeline/default.json" + ], + "policy_templates": [ + { + "name": "redis", + "title": "Redis logs and metrics", + "description": "Collect logs and metrics from Redis instances", + "inputs": [ + { + "type": "logfile", + "title": "Collect Redis application logs", + "description": "Collecting application logs from Redis instances" + }, + { + "type": "redis", + "title": "Collect Redis slow logs", + "description": "Collecting slow logs from Redis instances" + }, + { + "type": "redis/metrics", + "vars": [ + { + "name": "hosts", + "type": "text", + "title": "Hosts", + "multi": true, + "required": true, + "show_user": true, + "default": ["127.0.0.1:6379"] + }, + { + "name": "idle_timeout", + "type": "text", + "title": "Idle Timeout", + "multi": false, + "required": false, + "show_user": false, + "default": "20s" + }, + { + "name": "maxconn", + "type": "integer", + "title": "Maxconn", + "multi": false, + "required": false, + "show_user": false, + "default": 10 + }, + { + "name": "network", + "type": "text", + "title": "Network", + "multi": false, + "required": false, + "show_user": false, + "default": "tcp" + }, + { + "name": "username", + "type": "text", + "title": "Username", + "multi": false, + "required": false, + "show_user": false, + "default": "" + }, + { + "name": "password", + "type": "password", + "title": "Password", + "multi": false, + "required": false, + "show_user": false, + "default": "" + }, + { + "name": "ssl", + "type": "yaml", + "title": "SSL Configuration", + "description": "i.e. certificate_authorities, supported_protocols, verification_mode etc.", + "multi": false, + "required": false, + "show_user": false, + "default": "# ssl.certificate_authorities: |\n# -----BEGIN CERTIFICATE-----\n# MIID+jCCAuKgAwIBAgIGAJJMzlxLMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNVBAYT\n# AlVTMQwwCgYDVQQKEwNJQk0xFjAUBgNVBAsTDURlZmF1bHROb2RlMDExFjAUBgNV\n# BAsTDURlZmF1bHRDZWxsMDExGTAXBgNVBAsTEFJvb3QgQ2VydGlmaWNhdGUxEjAQ\n# BgNVBAMTCWxvY2FsaG9zdDAeFw0yMTEyMTQyMjA3MTZaFw0yMjEyMTQyMjA3MTZa\n# MF8xCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNJQk0xFjAUBgNVBAsTDURlZmF1bHRO\n# b2RlMDExFjAUBgNVBAsTDURlZmF1bHRDZWxsMDExEjAQBgNVBAMTCWxvY2FsaG9z\n# dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMv5HCsJZIpI5zCy+jXV\n# z6lmzNc9UcVSEEHn86h6zT6pxuY90TYeAhlZ9hZ+SCKn4OQ4GoDRZhLPTkYDt+wW\n# CV3NTIy9uCGUSJ6xjCKoxClJmgSQdg5m4HzwfY4ofoEZ5iZQ0Zmt62jGRWc0zuxj\n# hegnM+eO2reBJYu6Ypa9RPJdYJsmn1RNnC74IDY8Y95qn+WZj//UALCpYfX41hko\n# i7TWD9GKQO8SBmAxhjCDifOxVBokoxYrNdzESl0LXvnzEadeZTd9BfUtTaBHhx6t\n# njqqCPrbTY+3jAbZFd4RiERPnhLVKMytw5ot506BhPrUtpr2lusbN5svNXjuLeea\n# MMUCAwEAAaOBoDCBnTATBgNVHSMEDDAKgAhOatpLwvJFqjAdBgNVHSUEFjAUBggr\n# BgEFBQcDAQYIKwYBBQUHAwIwVAYDVR0RBE0wS4E+UHJvZmlsZVVVSUQ6QXBwU3J2\n# MDEtQkFTRS05MDkzMzJjMC1iNmFiLTQ2OTMtYWI5NC01Mjc1ZDI1MmFmNDiCCWxv\n# Y2FsaG9zdDARBgNVHQ4ECgQITzqhA5sO8O4wDQYJKoZIhvcNAQELBQADggEBAKR0\n# gY/BM69S6BDyWp5dxcpmZ9FS783FBbdUXjVtTkQno+oYURDrhCdsfTLYtqUlP4J4\n# CHoskP+MwJjRIoKhPVQMv14Q4VC2J9coYXnePhFjE+6MaZbTjq9WaekGrpKkMaQA\n# iQt5b67jo7y63CZKIo9yBvs7sxODQzDn3wZwyux2vPegXSaTHR/rop/s/mPk3YTS\n# hQprs/IVtPoWU4/TsDN3gIlrAYGbcs29CAt5q9MfzkMmKsuDkTZD0ry42VjxjAmk\n# xw23l/k8RoD1wRWaDVbgpjwSzt+kl+vJE/ip2w3h69eEZ9wbo6scRO5lCO2JM4Pr\n# 7RhLQyWn2u00L7/9Omw=\n# -----END CERTIFICATE-----\n" + } + ], + "title": "Collect Redis metrics", + "description": "Collecting info, key and keyspace metrics from Redis instances" + } + ], + "multiple": true + } + ], + "data_streams": [ + { + "type": "metrics", + "dataset": "redis.key", + "title": "Redis key metrics", + "release": "ga", + "streams": [ + { + "input": "redis/metrics", + "vars": [ + { + "name": "key.patterns", + "type": "yaml", + "title": "Key Patterns", + "multi": false, + "required": true, + "show_user": true, + "default": "- limit: 20\n pattern: '*'\n" + }, + { + "name": "period", + "type": "text", + "title": "Period", + "multi": false, + "required": true, + "show_user": true, + "default": "10s" + }, + { + "name": "processors", + "type": "yaml", + "title": "Processors", + "description": "Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the events are shipped. See [Processors](https://www.elastic.co/guide/en/fleet/current/elastic-agent-processor-configuration.html) for details. \n", + "multi": false, + "required": false, + "show_user": false + } + ], + "template_path": "stream.yml.hbs", + "title": "Redis key metrics", + "description": "Collect Redis key metrics", + "enabled": true + } + ], + "package": "redis", + "elasticsearch": {}, + "path": "key" + } + ] +} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts new file mode 100644 index 0000000000000..5ff46f358bbe7 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const REDIS_ASSETS_MAP = new Map([ + [ + 'redis-1.18.0/data_stream/slowlog/agent/stream/stream.yml.hbs', + Buffer.from(`hosts: +{{#each hosts as |host i|}} + - {{host}} +{{/each}} +password: {{password}} +{{#if processors}} +processors: +{{processors}} +{{/if}} +`), + ], + [ + 'redis-1.18.0/data_stream/log/agent/stream/stream.yml.hbs', + Buffer.from(`paths: +{{#each paths as |path i|}} + - {{path}} +{{/each}} +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#each tags as |tag i|}} + - {{tag}} +{{/each}} +{{#contains "forwarded" tags}} +publisher_pipeline.disable_host: true +{{/contains}} +exclude_files: [".gz$"] +exclude_lines: ["^\\s+[\\-\`('.|_]"] # drop asciiart lines\n +{{#if processors}} +processors: +{{processors}} +{{/if}} +`), + ], + [ + 'redis-1.18.0/data_stream/key/agent/stream/stream.yml.hbs', + Buffer.from(`metricsets: ["key"] +hosts: +{{#each hosts}} + - {{this}} +{{/each}} +{{#if idle_timeout}} +idle_timeout: {{idle_timeout}} +{{/if}} +{{#if key.patterns}} +key.patterns: {{key.patterns}} +{{/if}} +{{#if maxconn}} +maxconn: {{maxconn}} +{{/if}} +{{#if network}} +network: {{network}} +{{/if}} +{{#if username}} +username: {{username}} +{{/if}} +{{#if password}} +password: {{password}} +{{/if}} +{{#if ssl}} +{{ssl}} +{{/if}} +period: {{period}} +{{#if processors}} +processors: +{{processors}} +{{/if}} +`), + ], +]); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap new file mode 100644 index 0000000000000..b3a428c0e5a55 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Fleet - getTemplateInputs should work for input package 1`] = ` +"inputs: + # Custom log file: Collect your custom log files. + - id: logs-logfile + type: logfile + streams: + # Custom log file: Custom log file + - id: logfile-log.logs + data_stream: + dataset: <DATA_STREAM.DATASET> + # Dataset name: Set the name for your dataset. Changing the dataset will send the data to a different index. You can't use \`-\` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). + + paths: + - <PATHS> # Log file path: Path to log files to be collected + exclude_files: + - <EXCLUDE_FILES> # Exclude files: Patterns to be ignored + ignore_older: 72h + tags: + - <TAGS> # Tags: Tags to include in the published event +" +`; + +exports[`Fleet - getTemplateInputs should work for integration package 1`] = ` +"inputs: + # Collect Redis application logs: Collecting application logs from Redis instances + - id: redis-logfile + type: logfile + # Collect Redis slow logs: Collecting slow logs from Redis instances + - id: redis-redis + type: redis + # Collect Redis metrics: Collecting info, key and keyspace metrics from Redis instances + - id: redis-redis/metrics + type: redis/metrics + streams: + # Redis key metrics: Collect Redis key metrics + - id: redis/metrics-redis.key + data_stream: + dataset: redis.key + type: metrics + metricsets: + - key + hosts: + - 127.0.0.1:6379 + idle_timeout: 20s + key.patterns: + - limit: 20 + pattern: '*' + maxconn: 10 + network: tcp + username: <USERNAME> # Username + password: <PASSWORD> # Password + period: 10s +" +`; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts index 640fc3877eabf..8c63f4b093dd0 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts @@ -8,8 +8,14 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import { merge } from 'lodash'; import { dump } from 'js-yaml'; +import yamlDoc from 'yaml'; -import { packageToPackagePolicy } from '../../../../common/services/package_to_package_policy'; +import { getNormalizedInputs, isIntegrationPolicyTemplate } from '../../../../common/services'; + +import { + getStreamsForInputType, + packageToPackagePolicy, +} from '../../../../common/services/package_to_package_policy'; import { getInputsWithStreamIds, _compilePackagePolicyInputs } from '../../package_policy'; import { appContextService } from '../../app_context'; import type { @@ -17,6 +23,10 @@ import type { NewPackagePolicy, PackagePolicyInput, TemplateAgentPolicyInput, + RegistryVarsEntry, + RegistryStream, + PackagePolicyConfigRecordEntry, + RegistryInput, } from '../../../../common/types'; import { _sortYamlKeys } from '../../../../common/services/full_agent_policy_to_yaml'; @@ -27,6 +37,18 @@ import { getPackageAssetsMap } from './get'; type Format = 'yml' | 'json'; +type PackageWithInputAndStreamIndexed = Record< + string, + RegistryInput & { + streams: Record< + string, + RegistryStream & { + data_stream: { type: string; dataset: string }; + } + >; + } +>; + // Function based off storedPackagePolicyToAgentInputs, it only creates the `streams` section instead of the FullAgentPolicyInput export const templatePackagePolicyToFullInputStreams = ( packagePolicyInputs: PackagePolicyInput[] @@ -38,7 +60,7 @@ export const templatePackagePolicyToFullInputStreams = ( packagePolicyInputs.forEach((input) => { const fullInputStream = { // @ts-ignore-next-line the following id is actually one level above the one in fullInputStream, but the linter thinks it gets overwritten - id: input.policy_template ? `${input.type}-${input.policy_template}` : `${input.type}`, + id: input.policy_template ? `${input.policy_template}-${input.type}` : `${input.type}`, type: input.type, ...getFullInputStreams(input, true), }; @@ -81,22 +103,53 @@ export async function getTemplateInputs( prerelease?: boolean, ignoreUnverified?: boolean ) { - const packageInfoMap = new Map<string, PackageInfo>(); - let packageInfo: PackageInfo; - - if (packageInfoMap.has(pkgName)) { - packageInfo = packageInfoMap.get(pkgName)!; - } else { - packageInfo = await getPackageInfo({ - savedObjectsClient: soClient, - pkgName, - pkgVersion, - prerelease, - ignoreUnverified, - }); - } + const packageInfo = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName, + pkgVersion, + prerelease, + ignoreUnverified, + }); + const emptyPackagePolicy = packageToPackagePolicy(packageInfo, ''); + const inputsWithStreamIds = getInputsWithStreamIds(emptyPackagePolicy, undefined, true); + + const indexedInputsAndStreams = buildIndexedPackage(packageInfo); + + if (format === 'yml') { + // Add a placeholder <VAR_NAME> to all variables without default value + for (const inputWithStreamIds of inputsWithStreamIds) { + const inputId = inputWithStreamIds.policy_template + ? `${inputWithStreamIds.policy_template}-${inputWithStreamIds.type}` + : inputWithStreamIds.type; + + const packageInput = indexedInputsAndStreams[inputId]; + if (!packageInput) { + continue; + } + + for (const [inputVarKey, inputVarValue] of Object.entries(inputWithStreamIds.vars ?? {})) { + const varDef = packageInput.vars?.find((_varDef) => _varDef.name === inputVarKey); + if (varDef) { + addPlaceholderIfNeeded(varDef, inputVarValue); + } + } + for (const stream of inputWithStreamIds.streams) { + const packageStream = packageInput.streams[stream.id]; + if (!packageStream) { + continue; + } + for (const [streamVarKey, streamVarValue] of Object.entries(stream.vars ?? {})) { + const varDef = packageStream.vars?.find((_varDef) => _varDef.name === streamVarKey); + if (varDef) { + addPlaceholderIfNeeded(varDef, streamVarValue); + } + } + } + } + } + const assetsMap = await getPackageAssetsMap({ logger: appContextService.getLogger(), packageInfo, @@ -128,7 +181,146 @@ export async function getTemplateInputs( sortKeys: _sortYamlKeys, } ); - return yaml; + return addCommentsToYaml(yaml, buildIndexedPackage(packageInfo)); } + return { inputs: [] }; } + +function getPlaceholder(varDef: RegistryVarsEntry) { + return `<${varDef.name.toUpperCase()}>`; +} + +function addPlaceholderIfNeeded( + varDef: RegistryVarsEntry, + varValue: PackagePolicyConfigRecordEntry +) { + const placeHolder = `<${varDef.name.toUpperCase()}>`; + if (varDef && !varValue.value && varDef.type !== 'yaml') { + varValue.value = placeHolder; + } else if (varDef && varValue.value && varValue.value.length === 0 && varDef.type === 'text') { + varValue.value = [placeHolder]; + } +} + +function buildIndexedPackage(packageInfo: PackageInfo): PackageWithInputAndStreamIndexed { + return ( + packageInfo.policy_templates?.reduce<PackageWithInputAndStreamIndexed>( + (inputsAcc, policyTemplate) => { + const inputs = getNormalizedInputs(policyTemplate); + + inputs.forEach((packageInput) => { + const inputId = `${policyTemplate.name}-${packageInput.type}`; + + const streams = getStreamsForInputType( + packageInput.type, + packageInfo, + isIntegrationPolicyTemplate(policyTemplate) && policyTemplate.data_streams + ? policyTemplate.data_streams + : [] + ).reduce< + Record< + string, + RegistryStream & { + data_stream: { type: string; dataset: string }; + } + > + >((acc, stream) => { + const streamId = `${packageInput.type}-${stream.data_stream.dataset}`; + acc[streamId] = { + ...stream, + }; + return acc; + }, {}); + + inputsAcc[inputId] = { + ...packageInput, + streams, + }; + }); + return inputsAcc; + }, + {} + ) ?? {} + ); +} + +function addCommentsToYaml( + yaml: string, + packageIndexInputAndStreams: PackageWithInputAndStreamIndexed +) { + const doc = yamlDoc.parseDocument(yaml); + // Add input and streams comments + const yamlInputs = doc.get('inputs'); + if (yamlDoc.isCollection(yamlInputs)) { + yamlInputs.items.forEach((inputItem) => { + if (!yamlDoc.isMap(inputItem)) { + return; + } + const inputIdNode = inputItem.get('id', true); + if (!yamlDoc.isScalar(inputIdNode)) { + return; + } + const inputId = inputIdNode.value as string; + const pkgInput = packageIndexInputAndStreams[inputId]; + if (pkgInput) { + inputItem.commentBefore = ` ${pkgInput.title}${ + pkgInput.description ? `: ${pkgInput.description}` : '' + }`; + + yamlDoc.visit(inputItem, { + Scalar(key, node) { + if (node.value) { + const val = node.value.toString(); + for (const varDef of pkgInput.vars ?? []) { + const placeholder = getPlaceholder(varDef); + if (val.includes(placeholder)) { + node.comment = ` ${varDef.title}${ + varDef.description ? `: ${varDef.description}` : '' + }`; + } + } + } + }, + }); + + const yamlStreams = inputItem.get('streams'); + if (!yamlDoc.isCollection(yamlStreams)) { + return; + } + yamlStreams.items.forEach((streamItem) => { + if (!yamlDoc.isMap(streamItem)) { + return; + } + const streamIdNode = streamItem.get('id', true); + if (yamlDoc.isScalar(streamIdNode)) { + const streamId = streamIdNode.value as string; + const pkgStream = pkgInput.streams[streamId]; + if (pkgStream) { + streamItem.commentBefore = ` ${pkgStream.title}${ + pkgStream.description ? `: ${pkgStream.description}` : '' + }`; + yamlDoc.visit(streamItem, { + Scalar(key, node) { + if (node.value) { + const val = node.value.toString(); + for (const varDef of pkgStream.vars ?? []) { + const placeholder = getPlaceholder(varDef); + if (val.includes(placeholder)) { + node.comment = ` ${varDef.title}${ + varDef.description ? `: ${varDef.description}` : '' + }`; + } + } + } + }, + }); + } + } + }); + } + }); + } + + return doc.toString(); +} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts index ce80532b3b623..087002f212852 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts @@ -5,9 +5,19 @@ * 2.0. */ +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; + +import { createAppContextStartContractMock } from '../../../mocks'; import type { PackagePolicyInput } from '../../../../common/types'; +import { appContextService } from '../..'; + +import { getTemplateInputs, templatePackagePolicyToFullInputStreams } from './get_template_inputs'; +import REDIS_1_18_0_PACKAGE_INFO from './__fixtures__/redis_1_18_0_package_info.json'; +import { getPackageAssetsMap, getPackageInfo } from './get'; +import { REDIS_ASSETS_MAP } from './__fixtures__/redis_1_18_0_streams_template'; +import { LOGS_2_3_0_ASSETS_MAP, LOGS_2_3_0_PACKAGE_INFO } from './__fixtures__/logs_2_3_0'; -import { templatePackagePolicyToFullInputStreams } from './get_template_inputs'; +jest.mock('./get'); const packageInfoCache = new Map(); packageInfoCache.set('mock_package-0.0.0', { @@ -29,6 +39,9 @@ packageInfoCache.set('limited_package-0.0.0', { ], }); +packageInfoCache.set('redis-1.18.0', REDIS_1_18_0_PACKAGE_INFO); +packageInfoCache.set('log-2.3.0', LOGS_2_3_0_PACKAGE_INFO); + describe('Fleet - templatePackagePolicyToFullInputStreams', () => { const mockInput: PackagePolicyInput = { type: 'test-logs', @@ -189,7 +202,7 @@ describe('Fleet - templatePackagePolicyToFullInputStreams', () => { it('returns agent inputs without streams', async () => { expect(await templatePackagePolicyToFullInputStreams([mockInput2])).toEqual([ { - id: 'test-metrics-some-template', + id: 'some-template-test-metrics', type: 'test-metrics', streams: [ { @@ -305,3 +318,43 @@ describe('Fleet - templatePackagePolicyToFullInputStreams', () => { ]); }); }); + +describe('Fleet - getTemplateInputs', () => { + beforeEach(() => { + appContextService.start(createAppContextStartContractMock()); + jest.mocked(getPackageAssetsMap).mockImplementation(async ({ packageInfo }) => { + if (packageInfo.name === 'redis' && packageInfo.version === '1.18.0') { + return REDIS_ASSETS_MAP; + } + + if (packageInfo.name === 'log') { + return LOGS_2_3_0_ASSETS_MAP; + } + + return new Map(); + }); + jest.mocked(getPackageInfo).mockImplementation(async ({ pkgName, pkgVersion }) => { + const pkgInfo = packageInfoCache.get(`${pkgName}-${pkgVersion}`); + if (!pkgInfo) { + throw new Error('package not mocked'); + } + + return pkgInfo; + }); + }); + it('should work for integration package', async () => { + const soMock = savedObjectsClientMock.create(); + soMock.get.mockResolvedValue({ attributes: {} } as any); + const template = await getTemplateInputs(soMock, 'redis', '1.18.0', 'yml'); + + expect(template).toMatchSnapshot(); + }); + + it('should work for input package', async () => { + const soMock = savedObjectsClientMock.create(); + soMock.get.mockResolvedValue({ attributes: {} } as any); + const template = await getTemplateInputs(soMock, 'log', '2.3.0', 'yml'); + + expect(template).toMatchSnapshot(); + }); +}); diff --git a/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts b/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts index a1eac19eed8b7..cca480c45f56d 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts @@ -51,9 +51,11 @@ export default function (providerContext: FtrProviderContext) { await uninstallPackage(testPkgName, testPkgVersion); }); const expectedYml = `inputs: - - id: logfile-apache + # Collect logs from Apache instances: Collecting Apache access and error logs + - id: apache-logfile type: logfile streams: + # Apache access logs: Collect Apache access logs - id: logfile-apache.access data_stream: dataset: apache.access @@ -69,6 +71,7 @@ export default function (providerContext: FtrProviderContext) { target: '' fields: ecs.version: 1.5.0 + # Apache error logs: Collect Apache error logs - id: logfile-apache.error data_stream: dataset: apache.error @@ -84,9 +87,11 @@ export default function (providerContext: FtrProviderContext) { target: '' fields: ecs.version: 1.5.0 - - id: apache/metrics-apache + # Collect metrics from Apache instances: Collecting Apache status metrics + - id: apache-apache/metrics type: apache/metrics streams: + # Apache status metrics: Collect Apache status metrics - id: apache/metrics-apache.status data_stream: dataset: apache.status @@ -100,7 +105,7 @@ export default function (providerContext: FtrProviderContext) { `; const expectedJson = [ { - id: 'logfile-apache', + id: 'apache-logfile', type: 'logfile', streams: [ { @@ -151,7 +156,7 @@ export default function (providerContext: FtrProviderContext) { ], }, { - id: 'apache/metrics-apache', + id: 'apache-apache/metrics', type: 'apache/metrics', streams: [ { diff --git a/yarn.lock b/yarn.lock index 11778ed7abcc9..ec4c8f0e0837f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32974,6 +32974,11 @@ yaml@^2.0.0, yaml@^2.2.1, yaml@^2.2.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== +yaml@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== + yargs-parser@20.2.4, yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" From dbde89f1ed2e31d1de0f8ba9e45b513d7b45617e Mon Sep 17 00:00:00 2001 From: Jared Burgett <147995946+jaredburgettelastic@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:01:35 -0500 Subject: [PATCH 63/84] Fixed eslint 'Switch' to 'Routes' in Entity Analytics (#196433) Fixed a typing issue, due to a merge conflict related to ESLint changes --- .../security_solution/public/entity_analytics/routes.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx index 1cc1a24b020cb..7dc9970da5d9b 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx @@ -81,14 +81,14 @@ const EntityAnalyticsEntityStoreTelemetry = () => ( const EntityAnalyticsEntityStoreContainer: React.FC = React.memo(() => { return ( - <Switch> + <Routes> <Route path={ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH} exact component={EntityAnalyticsEntityStoreTelemetry} /> <Route component={NotFoundPage} /> - </Switch> + </Routes> ); }); From b4c3ab55a0680db2ec1a9d2f01051266f599e172 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi <maryam.saeidi@elastic.co> Date: Tue, 15 Oct 2024 22:05:11 +0200 Subject: [PATCH 64/84] [Related alerts] Add related alerts for all the observability rules (#195592) Closes #193942 Closes #193952 ## Summary This PR adds related alert logic for all the observability rules, as mentioned in #193942. Also, it adds a beta badge for this new tab. ![image](https://github.com/user-attachments/assets/43f7cf6a-670f-4a85-a11c-769d2b2f9625) --- .../alerting/get_related_alerts_query.test.ts | 80 +++++++++++++++- .../alerting/get_related_alerts_query.ts | 86 +++++++++++++++-- .../common/utils/alerting/types.ts | 14 +++ .../get_alerts_page_table_configuration.tsx | 7 +- .../register_alerts_table_configuration.tsx | 12 +++ .../alert_details_app_section.test.tsx | 11 --- .../alert_details_app_section.tsx | 13 +-- .../public/components/experimental_badge.tsx | 7 +- .../observability/public/constants.ts | 1 + .../pages/alert_details/alert_details.tsx | 32 ++++--- ...on_product_no_results_magnifying_glass.svg | 1 + .../components/related_alerts.tsx | 94 ++++++++++++++++--- 12 files changed, 294 insertions(+), 64 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability/common/utils/alerting/types.ts create mode 100644 x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/assets/illustration_product_no_results_magnifying_glass.svg diff --git a/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.test.ts b/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.test.ts index b7b8d138f471a..d5e6cd09dab00 100644 --- a/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.test.ts +++ b/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.test.ts @@ -5,25 +5,56 @@ * 2.0. */ -import { getRelatedAlertKuery } from './get_related_alerts_query'; +import { + getRelatedAlertKuery, + SERVICE_NAME, + MONITOR_ID, + OBSERVER_NAME, + HOST, + KUBERNETES_POD, + DOCKER_CONTAINER, + EC2_INSTANCE, + S3_BUCKETS, + RDS_DATABASES, + SQS_QUEUES, +} from './get_related_alerts_query'; import { fromKueryExpression } from '@kbn/es-query'; describe('getRelatedAlertKuery', () => { - const tags = ['tag1:v', 'tag2']; + const tags = ['tag1:v', 'tag2', 'apm']; const groups = [ { field: 'group1Field', value: 'group1Value' }, { field: 'group2Field', value: 'group2:Value' }, ]; + const ruleId = 'ruleUuid'; + const sharedFields = [ + { name: SERVICE_NAME, value: `my-${SERVICE_NAME}` }, + { name: MONITOR_ID, value: `my-${MONITOR_ID}` }, + { name: OBSERVER_NAME, value: `my-${OBSERVER_NAME}` }, + { name: HOST, value: `my-${HOST}` }, + { name: KUBERNETES_POD, value: `my-${KUBERNETES_POD}` }, + { name: DOCKER_CONTAINER, value: `my-${DOCKER_CONTAINER}` }, + { name: EC2_INSTANCE, value: `my-${EC2_INSTANCE}` }, + { name: S3_BUCKETS, value: `my-${S3_BUCKETS}` }, + { name: RDS_DATABASES, value: `my-${RDS_DATABASES}` }, + { name: SQS_QUEUES, value: `my-${SQS_QUEUES}` }, + ]; const tagsKuery = '(tags: "tag1:v" or tags: "tag2")'; const groupsKuery = '(group1Field: "group1Value" or kibana.alert.group.value: "group1Value") or (group2Field: "group2:Value" or kibana.alert.group.value: "group2:Value")'; + const ruleKuery = '(kibana.alert.rule.uuid: "ruleUuid")'; + const sharedFieldsKuery = + `(service.name: "my-service.name") or (monitor.id: "my-monitor.id") or (observer.name: "my-observer.name")` + + ` or (host.name: "my-host.name") or (kubernetes.pod.uid: "my-kubernetes.pod.uid") or (container.id: "my-container.id")` + + ` or (cloud.instance.id: "my-cloud.instance.id") or (aws.s3.bucket.name: "my-aws.s3.bucket.name")` + + ` or (aws.rds.db_instance.arn: "my-aws.rds.db_instance.arn") or (aws.sqs.queue.name: "my-aws.sqs.queue.name")`; it('should generate correct query with no tags or groups', () => { expect(getRelatedAlertKuery()).toBeUndefined(); }); it('should generate correct query for tags', () => { - const kuery = getRelatedAlertKuery(tags); + const kuery = getRelatedAlertKuery({ tags }); expect(kuery).toEqual(tagsKuery); // Should be able to parse keury without throwing error @@ -31,7 +62,7 @@ describe('getRelatedAlertKuery', () => { }); it('should generate correct query for groups', () => { - const kuery = getRelatedAlertKuery(undefined, groups); + const kuery = getRelatedAlertKuery({ groups }); expect(kuery).toEqual(groupsKuery); // Should be able to parse keury without throwing error @@ -39,10 +70,49 @@ describe('getRelatedAlertKuery', () => { }); it('should generate correct query for tags and groups', () => { - const kuery = getRelatedAlertKuery(tags, groups); + const kuery = getRelatedAlertKuery({ tags, groups }); expect(kuery).toEqual(`${tagsKuery} or ${groupsKuery}`); // Should be able to parse keury without throwing error fromKueryExpression(kuery!); }); + + it('should generate correct query for tags, groups and ruleId', () => { + const kuery = getRelatedAlertKuery({ tags, groups, ruleId }); + expect(kuery).toEqual(`${tagsKuery} or ${groupsKuery} or ${ruleKuery}`); + + // Should be able to parse keury without throwing error + fromKueryExpression(kuery!); + }); + + it('should generate correct query for sharedFields', () => { + const kuery = getRelatedAlertKuery({ sharedFields }); + expect(kuery).toEqual(sharedFieldsKuery); + + // Should be able to parse keury without throwing error + fromKueryExpression(kuery!); + }); + + it('should generate correct query when all the fields are provided', () => { + const kuery = getRelatedAlertKuery({ tags, groups, ruleId, sharedFields }); + expect(kuery).toEqual(`${tagsKuery} or ${groupsKuery} or ${sharedFieldsKuery} or ${ruleKuery}`); + + // Should be able to parse keury without throwing error + fromKueryExpression(kuery!); + }); + + it('should not include service.name twice', () => { + const serviceNameGroups = [{ field: 'service.name', value: 'myServiceName' }]; + const serviceNameSharedFields = [{ name: SERVICE_NAME, value: `my-${SERVICE_NAME}` }]; + const kuery = getRelatedAlertKuery({ + groups: serviceNameGroups, + sharedFields: serviceNameSharedFields, + }); + expect(kuery).toEqual( + `(service.name: "myServiceName" or kibana.alert.group.value: "myServiceName")` + ); + + // Should be able to parse keury without throwing error + fromKueryExpression(kuery!); + }); }); diff --git a/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.ts b/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.ts index cb2ad27bc8981..bd5be2b7822b0 100644 --- a/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.ts +++ b/x-pack/plugins/observability_solution/observability/common/utils/alerting/get_related_alerts_query.ts @@ -5,28 +5,102 @@ * 2.0. */ +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import type { Group } from '../../typings'; export interface Query { query: string; language: string; } +export interface Field { + name: string; + value: string; +} +interface Props { + tags?: string[]; + groups?: Group[]; + ruleId?: string; + sharedFields?: Field[]; +} + +// APM rules +export const SERVICE_NAME = 'service.name'; +// Synthetics rules +export const MONITOR_ID = 'monitor.id'; +// - location +export const OBSERVER_NAME = 'observer.name'; +// Inventory rule +export const HOST = 'host.name'; +export const KUBERNETES_POD = 'kubernetes.pod.uid'; +export const DOCKER_CONTAINER = 'container.id'; +export const EC2_INSTANCE = 'cloud.instance.id'; +export const S3_BUCKETS = 'aws.s3.bucket.name'; +export const RDS_DATABASES = 'aws.rds.db_instance.arn'; +export const SQS_QUEUES = 'aws.sqs.queue.name'; + +const ALL_SHARED_FIELDS = [ + SERVICE_NAME, + MONITOR_ID, + OBSERVER_NAME, + HOST, + KUBERNETES_POD, + DOCKER_CONTAINER, + EC2_INSTANCE, + S3_BUCKETS, + RDS_DATABASES, + SQS_QUEUES, +]; + +interface AlertFields { + [key: string]: any; +} + +export const getSharedFields = (alertFields: AlertFields = {}) => { + const matchedFields: Field[] = []; + ALL_SHARED_FIELDS.forEach((source) => { + Object.keys(alertFields).forEach((field) => { + if (source === field) { + const fieldValue = alertFields[field]; + matchedFields.push({ + name: source, + value: Array.isArray(fieldValue) ? fieldValue[0] : fieldValue, + }); + } + }); + }); + + return matchedFields; +}; + +const EXCLUDE_TAGS = ['apm']; -export const getRelatedAlertKuery = (tags?: string[], groups?: Group[]): string | undefined => { - const tagKueries: string[] = - tags?.map((tag) => { - return `tags: "${tag}"`; - }) ?? []; +export const getRelatedAlertKuery = ({ tags, groups, ruleId, sharedFields }: Props = {}): + | string + | undefined => { + const tagKueries = + tags + ?.filter((tag) => !EXCLUDE_TAGS.includes(tag)) + .map((tag) => { + return `tags: "${tag}"`; + }) ?? []; const groupKueries = (groups && groups.map(({ field, value }) => { return `(${field}: "${value}" or kibana.alert.group.value: "${value}")`; })) ?? []; + const ruleKueries = (ruleId && [`(${ALERT_RULE_UUID}: "${ruleId}")`]) ?? []; + const groupFields = groups?.map((group) => group.field) ?? []; + const sharedFieldsKueries = + sharedFields + ?.filter((field) => !groupFields.includes(field.name)) + .map((field) => { + return `(${field.name}: "${field.value}")`; + }) ?? []; const tagKueriesStr = tagKueries.length > 0 ? [`(${tagKueries.join(' or ')})`] : []; const groupKueriesStr = groupKueries.length > 0 ? [`${groupKueries.join(' or ')}`] : []; - const kueries = [...tagKueriesStr, ...groupKueriesStr]; + const kueries = [...tagKueriesStr, ...groupKueriesStr, ...sharedFieldsKueries, ...ruleKueries]; return kueries.length ? kueries.join(' or ') : undefined; }; diff --git a/x-pack/plugins/observability_solution/observability/common/utils/alerting/types.ts b/x-pack/plugins/observability_solution/observability/common/utils/alerting/types.ts new file mode 100644 index 0000000000000..ac68b45514bd2 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/common/utils/alerting/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ALERT_GROUP, TAGS } from '@kbn/rule-data-utils'; +import { Group } from '../../typings'; + +export interface ObservabilityFields { + [ALERT_GROUP]?: Group[]; + [TAGS]?: string[]; +} diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx index 30c912b510743..8282d90752d7c 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx @@ -34,7 +34,8 @@ export const getAlertsPageTableConfiguration = ( config: ConfigSchema, dataViews: DataViewsServicePublic, http: HttpSetup, - notifications: NotificationsStart + notifications: NotificationsStart, + id?: string ): AlertsTableConfigurationRegistry => { const renderCustomActionsRow = (props: RenderCustomActionsRowArgs) => { return ( @@ -46,7 +47,7 @@ export const getAlertsPageTableConfiguration = ( ); }; return { - id: ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID, + id: id ?? ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID, cases: { featureId: casesFeatureId, owner: [observabilityFeatureId] }, columns: getColumns({ showRuleName: true }), getRenderCellValue, @@ -66,7 +67,7 @@ export const getAlertsPageTableConfiguration = ( }, ruleTypeIds: observabilityRuleTypeRegistry.list(), usePersistentControls: getPersistentControlsHook({ - groupingId: ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID, + groupingId: id ?? ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID, featureIds: observabilityAlertFeatureIds, services: { dataViews, diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/register_alerts_table_configuration.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/register_alerts_table_configuration.tsx index de687c4dd7944..bc18c54d22ee3 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/register_alerts_table_configuration.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/register_alerts_table_configuration.tsx @@ -9,6 +9,7 @@ import { AlertTableConfigRegistry } from '@kbn/triggers-actions-ui-plugin/public import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types'; import { HttpSetup } from '@kbn/core-http-browser'; import { NotificationsStart } from '@kbn/core-notifications-browser'; +import { RELATED_ALERTS_TABLE_CONFIG_ID } from '../../constants'; import type { ConfigSchema } from '../../plugin'; import { ObservabilityRuleTypeRegistry } from '../..'; import { getAlertsPageTableConfiguration } from './alerts/get_alerts_page_table_configuration'; @@ -41,6 +42,17 @@ export const registerAlertsTableConfiguration = ( ); alertTableConfigRegistry.register(alertsPageAlertsTableConfig); + // Alert details page + const alertDetailsPageAlertsTableConfig = getAlertsPageTableConfiguration( + observabilityRuleTypeRegistry, + config, + dataViews, + http, + notifications, + RELATED_ALERTS_TABLE_CONFIG_ID + ); + alertTableConfigRegistry.register(alertDetailsPageAlertsTableConfig); + // Rule details page const ruleDetailsAlertsTableConfig = getRuleDetailsTableConfiguration( observabilityRuleTypeRegistry, diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx index de74fe2ec14b9..f45a353be9a61 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx @@ -76,7 +76,6 @@ jest.mock('../../../../utils/kibana_react', () => ({ describe('AlertDetailsAppSection', () => { const queryClient = new QueryClient(); - const mockedSetRelatedAlertsKuery = jest.fn(); const renderComponent = ( alert: Partial<CustomThresholdAlert> = {}, @@ -88,7 +87,6 @@ describe('AlertDetailsAppSection', () => { <AlertDetailsAppSection alert={buildCustomThresholdAlert(alert, alertFields)} rule={buildCustomThresholdRule()} - setRelatedAlertsKuery={mockedSetRelatedAlertsKuery} /> </QueryClientProvider> </IntlProvider> @@ -118,15 +116,6 @@ describe('AlertDetailsAppSection', () => { expect(mockedRuleConditionChart.mock.calls[0]).toMatchSnapshot(); }); - it('should set relatedAlertsKuery', async () => { - renderComponent(); - - expect(mockedSetRelatedAlertsKuery).toBeCalledTimes(1); - expect(mockedSetRelatedAlertsKuery).toHaveBeenLastCalledWith( - '(tags: "tag 1" or tags: "tag 2") or (host.name: "host-1" or kibana.alert.group.value: "host-1")' - ); - }); - it('should render title on condition charts', async () => { const result = renderComponent(); diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index 7885301650ecf..b474f246988b6 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -33,7 +33,6 @@ import moment from 'moment'; import { LOGS_EXPLORER_LOCATOR_ID, LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; import { TimeRange } from '@kbn/es-query'; import { getGroupFilters } from '../../../../../common/custom_threshold_rule/helpers/get_group'; -import { getRelatedAlertKuery } from '../../../../../common/utils/alerting/get_related_alerts_query'; import { useLicense } from '../../../../hooks/use_license'; import { useKibana } from '../../../../utils/kibana_react'; import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter'; @@ -49,15 +48,10 @@ import { generateChartTitleAndTooltip } from './helpers/generate_chart_title_and interface AppSectionProps { alert: CustomThresholdAlert; rule: CustomThresholdRule; - setRelatedAlertsKuery: React.Dispatch<React.SetStateAction<string | undefined>>; } // eslint-disable-next-line import/no-default-export -export default function AlertDetailsAppSection({ - alert, - rule, - setRelatedAlertsKuery, -}: AppSectionProps) { +export default function AlertDetailsAppSection({ alert, rule }: AppSectionProps) { const services = useKibana().services; const { charts, @@ -79,7 +73,6 @@ export default function AlertDetailsAppSection({ const alertStart = alert.fields[ALERT_START]; const alertEnd = alert.fields[ALERT_END]; const groups = alert.fields[ALERT_GROUP]; - const tags = alert.fields.tags; const chartTitleAndTooltip: Array<{ title: string; tooltip: string }> = []; @@ -112,10 +105,6 @@ export default function AlertDetailsAppSection({ const annotations: EventAnnotationConfig[] = []; annotations.push(alertStartAnnotation, alertRangeAnnotation); - useEffect(() => { - setRelatedAlertsKuery(getRelatedAlertKuery(tags, groups)); - }, [groups, setRelatedAlertsKuery, tags]); - useEffect(() => { setTimeRange(getPaddedAlertTimeRange(alertStart!, alertEnd)); }, [alertStart, alertEnd]); diff --git a/x-pack/plugins/observability_solution/observability/public/components/experimental_badge.tsx b/x-pack/plugins/observability_solution/observability/public/components/experimental_badge.tsx index 19d48b449f691..399e2b783eaaa 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/experimental_badge.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/experimental_badge.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBetaBadge } from '@elastic/eui'; +import { EuiBetaBadge, EuiBetaBadgeProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; @@ -23,7 +23,9 @@ export function ExperimentalBadge() { ); } -export function BetaBadge() { +export function BetaBadge( + badgeProps: Partial<Pick<EuiBetaBadgeProps, 'size' | 'iconType' | 'style'>> +) { return ( <EuiBetaBadge label={i18n.translate('xpack.observability.betaBadgeLabel', { @@ -32,6 +34,7 @@ export function BetaBadge() { tooltipContent={i18n.translate('xpack.observability.betaBadgeDescription', { defaultMessage: 'This functionality is in beta and is subject to change.', })} + {...badgeProps} /> ); } diff --git a/x-pack/plugins/observability_solution/observability/public/constants.ts b/x-pack/plugins/observability_solution/observability/public/constants.ts index 7af5d9380f6cc..b7a1ecea9c3f8 100644 --- a/x-pack/plugins/observability_solution/observability/public/constants.ts +++ b/x-pack/plugins/observability_solution/observability/public/constants.ts @@ -9,6 +9,7 @@ export const DEFAULT_INTERVAL = '60s'; export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm'; export const ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID = `alerts-page-alerts-table`; +export const RELATED_ALERTS_TABLE_CONFIG_ID = `related-alerts-table`; export const RULE_DETAILS_ALERTS_TABLE_CONFIG_ID = `rule-details-alerts-table`; export const SEARCH_BAR_URL_STORAGE_KEY = 'searchBarParams'; diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx index 6997e60e0a5af..8f5acee54f57e 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx @@ -6,8 +6,9 @@ */ import React, { useEffect, useState } from 'react'; -import { i18n } from '@kbn/i18n'; import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { EuiEmptyPrompt, EuiPanel, @@ -32,11 +33,12 @@ import dedent from 'dedent'; import { AlertFieldsTable } from '@kbn/alerts-ui-shared'; import { css } from '@emotion/react'; import { omit } from 'lodash'; +import { BetaBadge } from '../../components/experimental_badge'; +import { RelatedAlerts } from './components/related_alerts'; import { AlertDetailsSource } from './types'; import { SourceBar } from './components'; import { StatusBar } from './components/status_bar'; import { observabilityFeatureId } from '../../../common'; -import { RelatedAlerts } from './components/related_alerts'; import { useKibana } from '../../utils/kibana_react'; import { useFetchRule } from '../../hooks/use_fetch_rule'; import { usePluginContext } from '../../hooks/use_plugin_context'; @@ -109,7 +111,6 @@ export function AlertDetails() { const { euiTheme } = useEuiTheme(); const [sources, setSources] = useState<AlertDetailsSource[]>(); - const [relatedAlertsKuery, setRelatedAlertsKuery] = useState<string>(); const [activeTabId, setActiveTabId] = useState<TabId>(() => { const searchParams = new URLSearchParams(search); const urlTabId = searchParams.get(ALERT_DETAILS_TAB_URL_STORAGE_KEY); @@ -225,7 +226,6 @@ export function AlertDetails() { rule={rule} timeZone={timeZone} setSources={setSources} - setRelatedAlertsKuery={setRelatedAlertsKuery} /> <AlertHistoryChart alert={alertDetail.formatted} @@ -271,18 +271,22 @@ export function AlertDetails() { 'data-test-subj': 'metadataTab', content: metadataTab, }, - ]; - - if (relatedAlertsKuery && alertDetail?.formatted) { - tabs.push({ + { id: RELATED_ALERTS_TAB_ID, - name: i18n.translate('xpack.observability.alertDetails.tab.relatedAlertsLabel', { - defaultMessage: 'Related Alerts', - }), + name: ( + <> + <FormattedMessage + id="xpack.observability.alertDetails.tab.relatedAlertsLabe" + defaultMessage="Related alerts" + /> +   + <BetaBadge size="s" iconType="beta" style={{ verticalAlign: 'middle' }} /> + </> + ), 'data-test-subj': 'relatedAlertsTab', - content: <RelatedAlerts alert={alertDetail.formatted} kuery={relatedAlertsKuery} />, - }); - } + content: <RelatedAlerts alert={alertDetail?.formatted} />, + }, + ]; return ( <ObservabilityPageTemplate diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/assets/illustration_product_no_results_magnifying_glass.svg b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/assets/illustration_product_no_results_magnifying_glass.svg new file mode 100644 index 0000000000000..b9a0df1630b20 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/assets/illustration_product_no_results_magnifying_glass.svg @@ -0,0 +1 @@ +<svg fill="none" height="148" viewBox="0 0 200 148" width="200" xmlns="http://www.w3.org/2000/svg"><g fill="#e6ebf2"><path d="m66.493 121.253c.1447-.064.2618-.178.3302-.321.0685-.143.0837-.305.0431-.459-.1139-.178-.2841-.312-.4834-.382-.1994-.07-.4164-.072-.6166-.004-.1547.096-.2648.25-.3061.428-.0412.177-.0103.364.0861.518.0963.155.2502.265.4278.307.1775.041.3641.01.5189-.087z"/><path d="m46.666 68.1202c.12-.1134.3733-.44.2866-.6-.0866-.16-.5133-.0467-.6666 0-.1534.0466-.22.2666-.0734.4533.1467.1867.3.2933.4534.1467z"/><path d="m45.4062 81.1265c-.057-.0967-.1499-.1671-.2585-.1958s-.2241-.0134-.3215.0425c-.2467.1067-.46.2533-.46.5733 0 .9467.1733 1.16 1.1 1.3334h.0667c-.1215-.0187-.2452-.0187-.3667 0-.0457.0077-.0888.0264-.1256.0544-.0369.0281-.0664.0646-.0861.1065-.0197.042-.0289.0881-.0268.1343.002.0463.0152.0914.0385.1314.0124.0499.0359.0964.0688.1359s.0743.071.1212.0922c.0468.0212.0979.0314.1493.03.0513-.0013.1017-.0144.1474-.0381.12-.095.214-.2188.2733-.36.053.0902.1392.1561.2401.1835s.2086.0142.2999-.0368c.0652-.0378.1204-.0905.1611-.1539.0406-.0634.0656-.1356.0728-.2106.0071-.0749-.0037-.1506-.0316-.2205-.0279-.07-.0721-.1322-.129-.1817-.2266-.22-.1466-.4266-.12-.6666.7067.0466 1-.2667.88-.8667-.0147-.0587-.0325-.1165-.0533-.1733-.1467-.3934-.38-.4867-.7533-.3-.3734.1866-.6.3266-.4867.8733-.2-.1333-.3333-.1867-.4-.2867z"/><path d="m45.4194 80.0002c.091-.047.1596-.1281.1908-.2256.0313-.0975.0226-.2034-.0241-.2944-.0318-.1072-.0995-.2002-.1918-.2633-.0922-.0632-.2034-.0926-.3149-.0834-.0751.011-.1473.0374-.2118.0776-.0645.0401-.1201.0931-.1633.1556-.0431.0626-.0729.1333-.0875.2079s-.0137.1514.0026.2256c.0391.0659.0909.1235.1524.1693s.1315.079.2059.0976.1517.0223.2276.0108c.0758-.0115.1486-.0379.2141-.0777z"/><path d="m104.966 134.133c-.085.15-.112.327-.075.495.037.169.135.318.275.419.147.117.334.172.521.151s.358-.115.476-.262c.117-.146.172-.334.151-.521-.02-.187-.115-.358-.261-.475-.181-.074-.379-.095-.572-.061-.192.034-.371.123-.515.254z"/><path d="m47.0795 69.0398c-.0635.0231-.121.0603-.1681.1087-.0471.0485-.0826.1071-.1037.1713-.0212.0641-.0276.1323-.0186.1993s.0331.1311.0704.1874c.0657.1.165.1732.28.2062s.238.0237.3467-.0262c.1008-.0745.1726-.1818.203-.3034.0305-.1216.0176-.2501-.0364-.3633-.0525-.0995-.1422-.1743-.2496-.208-.1073-.0337-.2237-.0237-.3237.028z"/><path d="m46.9868 64.0001c.12-.0866.3466-.4.28-.5067-.0667-.1066-.4267-.1266-.58-.1-.1534.0267-.2467.26-.14.4867.0126.0441.0363.0842.069.1164.0326.0322.0729.0555.1172.0675.0442.0121.0908.0125.1352.0013.0445-.0111.0853-.0336.1186-.0652z"/><path d="m51.4726 108c-.1429.128-.233.305-.2527.496s.0323.382.146.537c.1749.138.3948.205.6168.189.2219-.016.4299-.114.5832-.276.064-.057.1152-.126.1503-.205.0351-.078.0532-.162.0532-.248s-.0181-.17-.0532-.248c-.0351-.079-.0863-.148-.1503-.205-.0659-.08-.1479-.145-.2406-.19-.0926-.046-.1939-.071-.2971-.075s-.2061.014-.3018.053c-.0958.039-.1823.097-.2538.172z"/><path d="m53.0133 100.154c.0244-.011.0457-.028.0624-.048.0166-.021.028-.046.0332-.072s.0041-.0528-.0032-.0784c-.0074-.0256-.0208-.0491-.039-.0684-.0096-.0255-.0255-.0482-.0462-.0658-.0207-.0177-.0456-.0297-.0723-.0351-.0267-.0053-.0544-.0038-.0803.0045-.0259.0084-.0493.0232-.0679.043-.0251.0112-.047.0284-.0638.0501-.0168.0216-.028.0471-.0327.0741-.0046.0271-.0024.055.0062.081.0087.026.0237.049.0436.068.0118.023.0286.042.0491.058.0206.015.0443.025.0694.029.0252.005.051.004.0755-.003s.0471-.02.066-.037z"/><path d="m49.7333 91.7797c.0712.1903.1605.3733.2667.5466-.0497.0839-.0942.1707-.1334.26-.0723.1653-.0945.3483-.0637.5261.0309.1778.1133.3426.2371.4739.1261.0971.2731.1634.4294.1935s.3174.0232.4706-.0201c-.0345.1648-.0345.3351 0 .5l.3133.1466c-.4067-1.12-.7667-2.2666-1.1067-3.42-.0638.0231-.1243.0545-.18.0934-.1104.0748-.1931.1839-.2353.3104-.0422.1266-.0415.2635.002.3896z"/><path d="m52.5402 102.127c.0525.017.1095.015.1608-.006.0513-.02.0935-.059.1192-.108.0172-.061.0156-.126-.0046-.187s-.0581-.114-.1087-.153c-.0934-.06-.24.06-.2867.154-.0467.093.0067.266.12.3z"/><path d="m52.7327 106.413c.0843-.096.1392-.214.1581-.341.0189-.126.001-.256-.0515-.372-.0264-.041-.0614-.077-.1025-.104-.0412-.026-.0876-.044-.1361-.052-.0485-.007-.0981-.005-.1455.009-.0473.013-.0914.036-.1292.067-.3133.213-.4533.447-.36.613.1.096.2238.163.3587.195s.2758.027.408-.015z"/><path d="m53.0528 104.127c.0915-.145.1338-.315.1206-.486-.0132-.17-.0813-.332-.1939-.461-.1356-.063-.2859-.087-.4342-.069s-.2888.076-.4058.169c-.0758.095-.117.212-.117.333 0 .122.0412.239.117.334.3533.32.7133.393.9133.18z"/><path d="m44.4995 73.3736c-.1873.0998-.331.2653-.4036.4647-.0726.1993-.069.4185.0102.6153.0828.1942.233.3518.423.4437s.4068.1119.6104.0563c.2009-.1305.3546-.3221.4386-.5464.084-.2244.0938-.4698.028-.7002-.1121-.1809-.2865-.3146-.4903-.3759-.2038-.0614-.4229-.0463-.6163.0425z"/><path d="m54.3862 101.46c-.1734-.326-.34-.666-.5067-.986-.2533.16-.3533.393-.2533.593.0915.116.2066.211.3376.278.1311.068.275.107.4224.115z"/><path d="m55.1802 106.173c-.12.26-.22.527.0867.667.3066.14.4733-.034.6-.274-.0117-.075-.0402-.146-.0832-.209-.0431-.062-.0997-.115-.1656-.152-.066-.038-.1396-.061-.2154-.066s-.1518.006-.2225.034z"/><path d="m51.0395 95.3332c-.12.0466-.1667.12-.1133.2466.0533.1267.1266.16.2533.1134.0245-.0065.0472-.0182.0667-.0344.0194-.0161.0351-.0364.0459-.0592.0108-.0229.0164-.0478.0166-.0731.0001-.0253-.0053-.0504-.0159-.0733-.0466-.12-.12-.1734-.2533-.12z"/><path d="m55.1867 108.2c-.0344.037-.0608.08-.0772.128-.0165.048-.0227.098-.0183.149.0044.05.0193.099.0438.143s.058.083.0984.113c.0419.047.0932.085.1507.11.0574.026.1197.039.1826.039.063 0 .1252-.013.1826-.039.0575-.025.1088-.063.1507-.11.038-.04.0672-.088.0859-.14s.0264-.107.0226-.162c-.0037-.055-.0189-.109-.0444-.158-.0256-.049-.061-.092-.1041-.127-.047-.042-.1021-.074-.162-.094-.0598-.02-.1231-.028-.186-.023-.063.005-.1243.023-.1802.052s-.1053.07-.1451.119z"/><path d="m55.6861 110.907c.0895.086.209.135.3333.135.1244 0 .2439-.049.3334-.135.0759-.082.1181-.189.1181-.3s-.0422-.219-.1181-.3c-.0325-.045-.0742-.083-.1225-.111-.0482-.028-.1019-.046-.1573-.051-.0555-.006-.1116 0-.1645.018-.0529.017-.1014.046-.1424.084-.0523.035-.0963.081-.1292.135s-.0539.114-.0614.177c-.0076.062-.0017.126.0174.186s.0509.115.0932.162z"/><path d="m54.3328 104.666c-.0296.04-.0501.086-.0602.134-.01.049-.0093.099.0022.148.0114.048.0333.093.064.132.0308.039.0697.071.114.093.3134.213.5867.253.6667.107.0513-.128.0672-.267.0461-.404-.0211-.136-.0785-.263-.1661-.37-.1152-.036-.2381-.04-.3555-.012s-.225.088-.3112.172z"/><path d="m37.2201 85.493c-.16-.3-.3533-.2934-.5133-.2067s-.3.26-.16.4333c.14.1734.34.22.4466.1667.1067-.0533.1734-.2867.2267-.3933z"/><path d="m38.3067 92.5403c-.133.0676-.2395.178-.3023.3134-.0629.1354-.0785.2879-.0443.4332.0751.1437.1955.2588.3424.3274.147.0687.3124.0872.4709.0526.26-.1067.3333-.5267.16-.9066-.0624-.104-.1592-.183-.2736-.2231-.1145-.0402-.2394-.0391-.3531.0031z"/><path d="m37.9193 89.1732c-.2733-.1066-.5133-.2733-.7467 0-.0713.0745-.1111.1736-.1111.2767s.0398.2022.1111.2767c.0509.0575.1135.1033.1837.1344.0702.031.1463.0466.223.0456.3333-.0867.38-.3667.34-.7334z"/><path d="m44.9994 100.606c-.26 0-.48.56-.3.794.18.233.6.293.74.106.14-.186-.1733-.84-.44-.9z"/><path d="m43.2135 95.5601-.2066.22c-.1273-.1332-.2631-.258-.4067-.3734-.0407-.0489-.0916-.0883-.1492-.1153-.0576-.0271-.1205-.0411-.1841-.0411-.0637 0-.1265.014-.1842.0411-.0576.027-.1085.0664-.1492.1153-.1093.0939-.1852.2206-.2165.3612-.0312.1406-.0161.2875.0432.4188.0516.1288.1462.2357.2678.3026.1215.0668.2625.0895.3989.0641.2014-.0187.4003-.059.5933-.12.0669.0714.1406.1361.22.1933.1426.1112.3151.1775.4955.1906s.3606-.0277.5178-.1172c.1411-.1137.2508-.2616.3185-.4296.0677-.1681.0912-.3507.0682-.5304-.0192-.1372-.0909-.2615-.2-.3467-.0891-.0754-.1931-.1312-.3051-.1638-.112-.0327-.2297-.0414-.3453-.0257-.1157.0157-.2267.0555-.326.1169-.0993.0613-.1845.1429-.2503.2393z"/><path d="m46.1728 105.334c-.0372.033-.0669.075-.0872.121-.0204.045-.0309.095-.0309.145 0 .051.0105.1.0309.146.0203.046.05.087.0872.121.0288.035.0643.063.1043.083.0401.021.0838.033.1286.036.0449.003.0899-.003.1323-.018.0425-.014.0816-.037.1149-.068.0421-.025.0784-.059.1065-.1.0282-.04.0476-.086.057-.134.0094-.049.0086-.098-.0023-.146-.011-.048-.0318-.094-.0613-.133-.0336-.043-.0756-.079-.1235-.105s-.1006-.042-.1549-.047c-.0544-.005-.1091.001-.161.018-.0518.017-.0997.045-.1406.081z"/><path d="m40.3331 86.1863c0-.3533-.2133-.4933-.5667-.5-.1933-.8267-.6666-.9267-1.3333-.3133-.0575-.014-.1134-.0342-.1667-.06-.0867-.0521-.19-.0693-.289-.0483-.0989.021-.1862.0788-.2443.1616-.0652.0637-.1086.1462-.1242.236s-.0024.1821.0375.264c.1534.4667.36.5667.82.38l1.04-.4c.2867.62.3667.64.8267.28z"/><path d="m41.3329 88.8003c-.0319-.115-.0894-.2211-.1683-.3105s-.1771-.1597-.2871-.2056c-.1101-.0459-.2291-.0661-.3481-.0593-.1191.0069-.235.0408-.339.099-.104.0583-.1935.1394-.2615.2373-.0681.0979-.113.21-.1314.3278s-.0098.2383.0252.3522c.035.114.0955.2185.1768.3057.1421.1708.3439.281.5643.3083.2205.0272.443-.0306.6224-.1616.0713-.0465.1322-.1074.1787-.1787.0465-.0714.0777-.1516.0914-.2357.0138-.084.01-.17-.0113-.2525-.0212-.0824-.0594-.1596-.1121-.2264z"/><path d="m41.9465 92.0001c-.0958-.1538-.2409-.2705-.4117-.331-.1707-.0606-.357-.0614-.5283-.0023-.1425.0952-.2468.2378-.2944.4025-.0475.1647-.0354.3409.0344.4975.1467.3067.4267.3667.82.1733.2667-.1333.4734-.5333.38-.74z"/><path d="m42.8393 97.9398c-.0974-.0154-.1971.0034-.2822.0533-.0851.0498-.1503.1276-.1845.22-.0116.0685-.0045.1389.0205.2037s.067.1216.1215.1646c.0546.043.1197.0705.1886.0796.0688.0091.1389-.0005.2027-.0279.0581-.0406.1043-.0958.134-.1602.0297-.0643.0418-.1354.035-.2059s-.0322-.138-.0736-.1955-.0973-.1029-.162-.1317z"/><path d="m44.3393 66.8467c.1-.0467.0933-.3.0533-.3667s-.1866-.1667-.2866-.1133c-.0485.0311-.0853.0774-.1046.1317-.0194.0543-.0201.1135-.0021.1683.0467.0733.2467.2333.34.18z"/><path d="m43.0463 75.7404c.1066.1067.3933.3534.54.26.1466-.0933.04-.4866 0-.6-.04-.1133-.2867-.0866-.4067-.04-.0426.0033-.0833.0189-.1171.0449s-.0594.0613-.0735.1016c-.0142.0403-.0163.0838-.0061.1253s.0322.0791.0634.1082z"/><path d="m42.7535 71.9601c.1419-.1152.2376-.2776.2694-.4576.0319-.18-.0022-.3655-.096-.5223-.1135-.1308-.2706-.2158-.4421-.2392-.1715-.0235-.3456.0162-.49.1116-.1444.0955-.2492.2402-.2948.4071-.0456.167-.0289.3449.0469.5004.1071.1599.2732.2707.462.3082.1887.0375.3845-.0014.5446-.1082z"/><path d="m41.2198 84.8734c-.2267.1133-.3067.3-.1467.48s.3133.46.56.3267c.2467-.1334.1133-.4867.0867-.6667-.0267-.18-.2934-.2467-.5-.14z"/><path d="m40.4998 81.8067c-.1534.1298-.2604.3061-.3049.5021s-.0241.4012.0582.5845c.0623.0916.1427.1694.2363.2286.0936.0591.1984.0984.3079.1152.1094.0169.2212.0111.3283-.0171s.2072-.0781.2942-.1467c.0867-.0389.1641-.0959.227-.1672s.1098-.1553.1376-.2462.0358-.1867.0235-.281c-.0123-.0942-.0446-.1848-.0948-.2656-.1241-.1972-.3193-.3391-.5453-.3962-.2259-.0571-.4651-.025-.668.0896z"/><path d="m45.086 72.0931c.1475-.0869.2614-.2211.3231-.3808.0617-.1598.0677-.3356.0169-.4992-.0591-.118-.1608-.2092-.2846-.255s-.2604-.0428-.3821.0084c-.1378.0846-.2402.2165-.2881.371-.0478.1546-.0378.3212.0281.469.0179.0585.0477.1128.0874.1594.0398.0465.0887.0844.1437.1113s.115.0422.1761.0449c.0612.0028.1223-.0071.1795-.029z"/><path d="m45.5861 69.7262c.0263-.021.0441-.0507.0502-.0838.0061-.033.0001-.0672-.0168-.0962 0-.04-.14-.0466-.1667 0-.0136.0088-.0253.0201-.0345.0334s-.0156.0282-.0191.044c-.0034.0158-.0036.0321-.0007.048s.0089.0311.0176.0446c.0088.0136.0201.0253.0334.0345s.0282.0157.044.0191.0321.0036.048.0007.0311-.0089.0446-.0176z"/><path d="m43.7065 77.4263c.1036.1215.2474.2019.4052.2263.1579.0245.3192-.0085.4548-.093.3866-.22.5-.4667.34-.7667-.0917-.1545-.2343-.2724-.4034-.3332s-.3542-.0609-.5233-.0001c-.1428.1109-.2463.2645-.2955.4384-.0492.174-.0414.3591.0222.5283z"/><path d="m40.7993 74.7869c.12-.1133.3867-.4467.2933-.5867-.0933-.14-.4933-.0866-.6666-.0466-.1734.04-.2134.3466-.0934.52.12.1733.3334.2599.4667.1133z"/><path d="m38.3535 72.9266c0 .04.18.1334.22.1067s.2467-.14.2-.2867c-.0466-.1467-.2667-.12-.3-.0867-.0722.0706-.1151.1659-.12.2667z"/><path d="m38.5999 76.4801c.046.1061.1322.1895.2398.2321.1075.0425.2275.0405.3336-.0055.1061-.0459.1895-.1322.2321-.2397.0425-.1075.0405-.2275-.0055-.3336-.0669-.0996-.1633-.1756-.2757-.2175-.1125-.0418-.2352-.0474-.3509-.0158-.0953.0583-.1657.1498-.1977.2568s-.0233.2221.0243.3232z"/><path d="m40.9266 65.8929c.0734-.04.0734-.2067.0534-.3s-.1734-.1933-.2867-.1333-.0533.2733 0 .36c.0288.032.0656.0557.1066.0686.0411.0129.0849.0145.1267.0047z"/><path d="m37.9998 71.1003c.1113.0157.2246-.0095.3189-.0707.0943-.0613.1634-.1545.1944-.2626.0428-.0806.052-.1747.0258-.2621-.0262-.0873-.0858-.1608-.1658-.2046-.2933-.1933-.52-.0466-.7667.16.06.3.0467.6067.3934.64z"/><path d="m40.7593 79.3996c.1105-.1034.1845-.2398.211-.3889.0264-.149.0038-.3026-.0644-.4378-.1309-.0746-.2832-.1027-.4321-.0797s-.2856.0957-.3879.2064c-.0445.0933-.0591.1981-.0416.3001.0175.1019.0661.1958.1393.269.0731.0731.1671.1217.269.1392s.2067.0029.3-.0416z"/><path d="m38.3734 80.6665c.2867-.16.3667-.38.2333-.6666-.0815-.1728-.2236-.3097-.3993-.3846-.1758-.075-.3729-.0828-.554-.0221-.1239.1051-.2052.2519-.2285.4127-.0234.1608.0129.3246.1018.4606.0878.1365.2251.2336.383.2709s.3241.0119.4637-.0709z"/><path d="m45.6061 107.934c-.1334 0-.2867.266-.4134.433-.0197.017-.0355.038-.0463.061-.0109.024-.0165.05-.0165.076s.0056.051.0165.075c.0108.023.0266.044.0463.061.107.058.2298.08.3501.062s.2314-.075.3166-.162c.0333-.055.0531-.116.0579-.18s-.0055-.128-.0302-.188c-.0247-.059-.0631-.111-.1119-.153-.0488-.041-.1068-.071-.1691-.085z"/><path d="m79.2334 128.127c-.1266-.254-.4666-.32-.8466-.154-.28.134-.4867.507-.3934.72.0874.159.2232.285.3876.361.1644.075.3486.096.5258.059.0426-.017.083-.039.12-.066-.0331.126-.0331.26 0 .386.1.329.3093.614.5933.807.1003.077.1766.181.22.3.0114.098.0472.191.104.271s.1328.145.2211.188c.0882.043.1859.063.284.059.0981-.005.1935-.034.2776-.085-.0065.058.0052.116.0333.167.0794.095.1901.159.3124.18.1222.021.2479-.003.3543-.067.1279-.049.2319-.146.2902-.27.0584-.124.0666-.266.0231-.396-.0291-.053-.0686-.1-.1162-.138-.0475-.038-.1021-.065-.1605-.081-.0584-.017-.1195-.021-.1796-.013s-.1181.028-.1704.058c-.0191-.153-.0789-.298-.1733-.42-.1493-.158-.2306-.369-.2267-.586 0-.36-.2533-.58-.5-.78-.1357-.077-.2902-.113-.4457-.106s-.306.057-.4343.146l-.0666.06c.0687-.197.0567-.413-.0334-.6z"/><path d="m89.2063 134.106c.1.2.4133.14.5533.114.0453-.004.0891-.018.1281-.041.0391-.023.0724-.055.0974-.093s.0412-.081.0471-.126c.006-.045.0017-.09-.0126-.134-.06-.213-.34-.4-.5-.28s-.4133.36-.3133.56z"/><path d="m93.4526 132.774c-.0796.033-.1511.084-.2098.147-.0586.064-.1029.139-.1301.221-.0271.082-.0364.169-.0272.255.0091.086.0366.169.0804.243.1734.354.5134.52.7734.374.1715-.108.3012-.271.3679-.463.0666-.191.0662-.4-.0013-.591-.0959-.128-.235-.218-.3918-.252-.1567-.035-.3206-.011-.4615.066z"/><path d="m93.6128 135.74c-.12.193.0933.427.1933.533.0274.036.0627.065.1032.085.0404.02.085.031.1301.031.0452 0 .0898-.011.1302-.031.0405-.02.0758-.049.1032-.085.1467-.166.16-.5 0-.573s-.54-.147-.66.04z"/><path d="m90.1928 131.573c-.0227-.064-.0594-.122-.1074-.169-.048-.048-.1061-.085-.1701-.107-.0639-.022-.1321-.03-.1995-.023-.0673.008-.1322.03-.1897.066-.0752.04-.138.101-.1818.174s-.067.157-.067.243c0 .085.0232.169.067.242.0438.074.1066.134.1818.174-.0062.06.0083.121.041.172s.0817.089.139.108c.1415.02.2851.02.4267 0 .0392-.063.0626-.135.0684-.209s-.0062-.149-.0351-.217c.041-.068.0648-.145.0695-.224.0046-.079-.0101-.158-.0428-.23z"/><path d="m75.9995 127.926c.34-.167.4867-.467.3533-.72-.1094-.17-.2722-.299-.4629-.366-.1906-.068-.3983-.07-.5904-.007-.115.099-.1929.234-.2208.383-.0278.149-.004.303.0675.437.0306.08.079.153.1414.212.0625.059.1375.104.2195.13s.1688.034.254.022c.0853-.012.1667-.043.2384-.091z"/><path d="m66.7799 124.22c.0945.071.204.119.32.14.28-.146.3734-.406.2334-.546-.0545-.051-.1186-.09-.1885-.115-.0698-.025-.1441-.036-.2182-.032-.1933.033-.26.4-.1467.553z"/><path d="m71.5127 123.413c.3-.106.5533-.446.48-.666-.1467-.46-.6667-.454-.92-.44-.2534.013-.38.613-.14.96.0603.093.1536.16.2612.187.1075.027.2214.013.3188-.041z"/><path d="m114.093 132.72c-.3.113-.22.366-.167.606.26.12.494.14.667-.126.021-.06.028-.124.018-.187-.009-.063-.034-.122-.071-.173-.06-.054-.132-.093-.209-.113-.078-.021-.159-.024-.238-.007z"/><path d="m113.56 134.593c.113.073.206.167.353.147s.2-.234.087-.4c-.115-.152-.285-.253-.474-.28-.032-.004-.066 0-.096.012-.031.011-.059.029-.082.053-.022.024-.039.053-.049.084-.009.032-.011.065-.006.097.021.065.056.124.102.173.046.05.102.089.165.114z"/><path d="m116.073 130.46c.353.047.493-.187.626-.487-.093-.113-.173-.233-.26-.32l-.666.147c-.043.094-.061.197-.054.3 0 .047.008.094.025.137.018.044.044.084.077.117.032.034.072.06.115.078.043.019.09.028.137.028z"/><path d="m121.213 128.873c.033 0 .14 0 .16-.053.02-.054 0-.127 0-.16-.026-.025-.061-.039-.097-.039s-.07.014-.097.039c-.023.033-.033.073-.027.113s.028.076.061.1z"/><path d="m114.58 132.12c.201-.015.389-.103.528-.248.139-.146.219-.337.226-.538-.044-.2-.16-.377-.325-.498s-.369-.177-.573-.159c-.204.019-.394.112-.534.261-.141.149-.222.344-.228.549-.034.347.44.6.906.633z"/><path d="m117.56 135.086c-.046.048-.072.111-.072.177s.026.129.072.177c.133.126.28-.06.347-.127.066-.067 0-.227-.034-.24-.049-.023-.103-.034-.158-.032-.054.003-.108.018-.155.045z"/><path d="m118.033 130.46c0-.147 0-.433-.114-.473-.074-.01-.15-.002-.221.023-.071.026-.135.068-.186.123-.06.087-.053.44.054.507.106.067.433-.113.467-.18z"/><path d="m107.713 134.32c-.029.052-.047.11-.052.17-.005.059.002.119.021.176.02.056.051.108.092.152s.09.078.145.102c.194.093.407.2.534 0 .206-.334.186-.62 0-.76-.057-.045-.123-.076-.194-.092-.07-.016-.143-.016-.214-.001-.07.015-.137.046-.194.089-.057.044-.105.1-.138.164z"/><path d="m107.333 132.207c-.051.139-.053.292-.007.433.045.142.137.264.26.347.048.028.101.046.155.052.055.006.11 0 .163-.017.052-.017.1-.045.141-.082s.073-.083.095-.133c.233-.354.253-.627.073-.767-.145-.069-.309-.09-.467-.06s-.303.109-.413.227z"/><path d="m103.893 136.267c-.031.044-.053.095-.063.149s-.009.11.005.163c.013.053.038.103.072.145.034.043.077.078.126.103.109.062.236.084.359.059.123-.024.232-.093.308-.193.053-.108.065-.231.035-.347-.031-.117-.102-.218-.202-.286-.055-.031-.116-.05-.178-.056-.063-.006-.126.001-.186.02-.06.02-.116.051-.163.093-.048.042-.086.092-.113.15z"/><path d="m112 131.2c.155.096.341.13.52.095.179-.034.338-.135.447-.282.096-.209.112-.446.046-.666l-1.4.213c.015.128.057.252.123.362.067.111.157.206.264.278z"/><path d="m110.253 133.193c.051.033.108.055.169.064.06.009.121.005.179-.012.059-.018.113-.047.158-.087.046-.04.083-.089.107-.145.267-.413.254-.7-.046-.893-.147-.059-.308-.069-.461-.031s-.291.124-.393.244c-.037.156-.031.319.02.471s.143.287.267.389z"/><path d="m50.8796 97.4535c.0122.0368.0122.0765 0 .1133-.0559.0213-.1095.0481-.16.08-.0734-.1-.2267-.12-.4867.04-.0623.0246-.1186.0623-.1651.1107-.0465.0483-.082.106-.1041.1693-.0221.0632-.0304.1305-.0242.1972.0062.0668.0267.1314.0601.1895.0604.1218.1567.2222.2759.2878.1191.0655.2555.093.3907.0789.0506.0557.115.0971.1867.1199.2467.0734.4867.2534.6667.1534.0626-.0296.1174-.0732.1602-.1276s.0724-.118.0864-.1857c.0759-.1286.1064-.2788.0867-.4267-.0328-.1366-.0917-.2657-.1733-.38.0067-.071.0067-.1424 0-.2133-.0334-.2667-.0934-.6667-.46-.6134-.3667.0534-.3667.2867-.34.4067z"/><path d="m46.8258 73.2064c-.3 0-.6133 0-.6133.4334-.0089.1105.0208.2208.084.3119.0632.0912.156.1576.2626.1881.0451.0204.0939.031.1434.031.0494 0 .0983-.0106.1433-.031.1467-.1667.2667-.36.4133-.56.2344-.0052.4637-.0694.6667-.1867 0-.2933 0-.5933 0-.8867-.1614-.1572-.3749-.2497-.6-.26-.4-.02-.5.18-.5.96z"/><path d="m47.9061 84.4203c.0759.099.1827.1699.3035.2013s.2486.0215.3631-.028c0 0 0 0 .04-.0467-.0533-.28-.1066-.56-.1533-.84-.1384-.0467-.2883-.0467-.4267 0-.058.0361-.1079.084-.1462.1405-.0383.0566-.0643.1206-.0763.1879-.0119.0673-.0096.1364.007.2027.0165.0663.0468.1284.0889.1823z"/><path d="m47.0396 76.4866c.0635-.006.1246-.0273.1781-.0622.0534-.0348.0975-.0821.1286-.1378.0933-.28-.0667-.4534-.3467-.56-.2.1266-.4466.2466-.3333.5466.0428.0608.0984.1114.1629.1483.0645.0368.1364.0591.2104.0651z"/><path d="m49.2461 93.8936c.0866.1533.2066.3.4266.2133.1124-.0643.195-.1703.2299-.2951.035-.1247.0194-.2582-.0432-.3716-.1611-.0271-.3256-.0271-.4867 0-.2.0734-.2266.2734-.1266.4534z"/><path d="m49.0668 86.833c-.0481.1125-.0599.2373-.0336.3569.0263.1195.0894.2278.1803.3098-.0467-.24-.1067-.4534-.1467-.6667z"/><path d="m48.666 90.2132c.1732.0912.3731.1185.5645.077.1914-.0414.3621-.149.4821-.3037.0428-.0986.0611-.2061.0534-.3133-.04-.14-.0867-.2734-.12-.4134-.0832-.1646-.2264-.2911-.4-.3533-.1719-.0707-.3623-.083-.5418-.0348-.1796.0482-.3382.1541-.4516.3015-.22.2666.0267.7466.4134 1.04z"/><path d="m47.5263 77.6201c-.4067.0933-.8733.2466-.9733.74-.0179.16-.0017.3219.0475.4752s.1303.2944.238.4141c.1077.1196.2396.2151.3868.2802.1473.065.3067.0981.4677.0971.0683-.0061.1371.0062.1992.0355s.1153.0746.1541.1312c-.0867-1.0667-.14-2.1333-.1667-3.2133-.1489.3353-.2672.6833-.3533 1.04z"/><path d="m50.9331 102.426c.0214-.01.0403-.024.0553-.043.015-.018.0257-.039.0315-.062.0057-.023.0063-.047.0016-.07-.0046-.024-.0143-.046-.0284-.065-.06-.073-.1867-.213-.3333-.153-.1467.06-.06.287 0 .32s.1533.147.2733.073z"/><path d="m50.6663 95.3331c-.009-.0977-.0519-.1893-.1213-.2587s-.161-.1123-.2587-.1213c-.2334 0-.3.14-.28.62.0546.0454.1202.0757.1902.0879.0699.0122.1419.0058.2087-.0185.0667-.0242.1259-.0656.1718-.1199.0458-.0542.0766-.1196.0893-.1895z"/><path d="m49.5996 101.153c.0087.034.0246.066.0467.093.0424.055.097.099.1595.129s.1311.046.2005.045c.1266 0 .2533.066.4333 0 .1324-.064.2483-.157.3386-.273.0902-.115.1523-.25.1814-.394.0155-.127-.0028-.255-.0532-.372s-.1309-.219-.2335-.295c-.091-.06-.1964-.0949-.3054-.1007-.109-.0059-.2176.0177-.3146.0677-.1093.047-.2055.12-.2799.213s-.1248.203-.1467.32c-.0623-.023-.1311-.023-.1934 0-.3066.074-.6666.067-.9066.38l.04.454c-.3134.28-.4667.666-.3267.86.0528.079.131.138.2219.167s.189.027.2781-.007c.3667-.12.4467-.26.4133-.72.18-.24.3134-.387.4467-.567z"/><path d="m48.8398 104.667c.2467-.24.22-.527-.08-.82-.0357-.052-.0817-.097-.1354-.13-.0536-.034-.1135-.056-.1761-.065-.0626-.01-.1264-.006-.1875.01-.0611.017-.1182.045-.1676.085-.1068.138-.1743.303-.1955.477-.0211.173.005.35.0755.51.2733.28.5666.253.8666-.067z"/><path d="m62.7397 119.267c0-.134-.2466-.174-.32-.154-.0518.019-.0976.051-.1328.093s-.0584.093-.0672.147c0 .107.18.174.2667.174s.2667-.134.2533-.26z"/><path d="m48.2866 95.6997c-.0182.015-.0328.0339-.0428.0552-.0101.0213-.0153.0446-.0153.0681 0 .0236.0052.0469.0153.0682.01.0213.0246.0402.0428.0552.06.0666.2333.22.3333.1333s0-.28 0-.3133c0-.0334-.2133-.16-.3333-.0667z"/><path d="m48.1469 96.8662c.1133 0 .28.1667.16.2067s-.1867.1867-.0934.4933c.0101.0655.0337.1281.0694.1839.0357.0557.0828.1034.1381.1398.0553.0365.1176.0609.1829.0718.0653.0108.1322.0078.1963-.0088.1787-.0428.3334-.1542.4307-.3101.0973-.156.1294-.3439.0893-.5232-.08-.38-.3066-.4-.3333-.6667s-.18-.3133-.4467-.3533-.6667-.0734-.7067.3c-.04.3733.2.4666.3134.4666z"/><path d="m48.3197 86.1664c.2667-.1533.2734-.5533 0-.9666-.0873-.0958-.2031-.1611-.3303-.1863s-.2591-.009-.3763.0463c-.1107.0999-.1939.2265-.2417.3677-.0479.1412-.0588.2923-.0317.4389.1081.1503.2625.261.4395.3152.177.0541.3668.0488.5405-.0152z"/><path d="m46.9933 88.3529c-.1616-.1865-.3718-.3246-.6072-.3987-.2354-.0742-.4867-.0815-.7261-.0213-.1762.1197-.3013.301-.3507.5083-.0493.2072-.0193.4255.084.6117.1248.1837.3075.3203.519.3879.2115.0677.4395.0626.6477-.0145.1972-.0879.3526-.249.4334-.4492.0809-.2002.0808-.424-.0001-.6242z"/><path d="m42.96 78.9199c.0312-.1742.0312-.3525 0-.5267-.0734-.2467-.4334-.2933-.6667-.14-.0454.0318-.0839.0724-.1133.1194-.0293.047-.049.0994-.0577.1542-.0087.0547-.0063.1106.007.1644.0134.0538.0374.1044.0707.1487.0921.1085.2224.1774.364.1923.1415.0149.2833-.0254.396-.1123z"/><path d="m45.5793 96.8004c-.06.2266.0667.3266.5333.42.0519-.0417.0925-.0957.1182-.1571.0257-.0613.0356-.1281.029-.1943s-.0296-.1297-.067-.1847c-.0373-.0551-.0878-.1-.1468-.1306-.0943-.0184-.192-.004-.2769.0409-.085.0449-.1519.1175-.1898.2058z"/><path d="m42.0666 80.4133c.0728.1032.1821.1751.3058.2011.1236.0259.2526.0041.3608-.0611.0955-.0697.1619-.1722.1865-.2879.0246-.1156.0057-.2363-.0531-.3388-.0749-.1009-.1845-.1704-.3077-.1951-.1232-.0246-.2511-.0026-.359.0618-.0975.0665-.1658.1679-.1906.2833s-.0043.236.0573.3367z"/><path d="m46.0329 92.9536c-.0538-.0171-.1106-.0227-.1668-.0167-.0561.0061-.1104.0239-.1593.0521s-.0914.0663-.1248.1119c-.0334.0455-.0569.0975-.0691.1527-.0189.0812-.0185.1657.0012.2468.0197.081.0581.1563.1122.2199.26.2066.52.0733.7466-.1467-.0066-.24-.04-.54-.34-.62z"/><path d="m45.3334 91.0667c.24-.2067.12-.4667 0-.7467-.2934 0-.6-.1-.7267.2467-.0065.033-.0065.067 0 .1-.1339-.0477-.2761-.0682-.418-.0602-.142.0081-.2809.0444-.4087.1069-.0489.0333-.0908.0761-.123.1259-.0323.0497-.0544.1053-.0649.1636-.0106.0584-.0094.1182.0034.1761s.037.1126.0712.161c.0727.1525.2005.2717.3576.3336.1572.0619.332.0618.4891-.0002.0939-.054.1748-.128.237-.2167s.1042-.19.123-.2967c.0774.0311.1622.0389.244.0223.0817-.0166.1568-.0568.216-.1156z"/><path d="m47.4735 93.3731c.6667.72 1.2533.3467 1.6533-.16.1355-.1683.2106-.3773.2134-.5933-.0065-.1434-.0439-.2837-.1094-.4114-.0656-.1277-.1579-.2398-.2706-.3286-.2467-.2267-.2534-.5667-.5667-.6667-.2801-.1068-.5875-.119-.8752-.0347s-.5399.2604-.7181.5014c-.1199.1416-.2023.311-.2395.4927-.0373.1818-.0283.3699.0261.5473.1267.36.62.3467.8867.6533z"/><path d="m43.5266 82.7732c.28-.16.32-.4667.1066-.8467-.2133-.38-.46-.4333-.6666-.2933-.1269.1208-.2251.2686-.2874.4325-.0623.1638-.0871.3395-.0726.5141.0967.1473.2477.2502.4201.2865.1724.0362.3521.0027.4999-.0931z"/><path d="m45.4528 86.9932c.0927-.0811.161-.1864.1975-.3041s.0397-.2432.0091-.3625c-.027-.0603-.0666-.114-.1163-.1576-.0496-.0436-.108-.0759-.1713-.0949-.0632-.019-.1298-.0241-.1952-.0151-.0654.0091-.1281.0321-.1838.0676-.1047.0601-.1848.1553-.2262.2687-.0415.1134-.0417.2378-.0005.3513.0825.098.1895.1724.3101.2157s.2505.054.3766.0309z"/><path d="m43.8333 92.6666c-.17.091-.3018.2399-.3715.4197-.0697.1797-.0727.3785-.0085.5604.1122.1698.2835.2918.4806.3423.1972.0505.4061.0259.5861-.069.064-.0384.1199-.089.1643-.1491.0444-.06.0766-.1282.0945-.2007.018-.0725.0214-.1478.0102-.2217-.0112-.0738-.037-.1447-.0757-.2085-.1533-.32-.6333-.5734-.88-.4734z"/><path d="m43.1398 86.0331c.1133.2133.78.2866 1.0467.1133.0919-.0898.1559-.2043.1843-.3297s.02-.2563-.0243-.3769c-.0739-.1513-.2022-.2691-.3592-.3297-.1571-.0606-.3312-.0595-.4875.003-.1598.0853-.2839.2248-.3499.3935s-.0696.3554-.0101.5265z"/><path d="m44.1659 89.0466c.1152-.0436.2189-.113.3031-.2029.0843-.0899.1468-.1978.1828-.3156.0361-.1178.0447-.2422.0252-.3638-.0196-.1216-.0667-.2372-.1377-.3377-.124-.1975-.3198-.3391-.5461-.395-.2264-.056-.4656-.0219-.6673.0949-.1678.1612-.2787.3726-.3159.6022-.0373.2297.001.4652.1093.6712.0498.0878.1172.1643.198.2248.0808.0606.1732.1037.2715.1269.0982.0232.2002.0258.2995.0077s.1938-.0565.2776-.1127z"/><path d="m93.2926 134.153c.0333-.234-.38-.667-.6667-.707-.2866-.04-.5266-.08-.6666.253-.14.334-.2267.474-.3467.714-.0483.036-.0875.083-.1145.136-.027.054-.0411.114-.0411.174s.0141.119.0411.173.0662.101.1145.137c.0973.076.2192.113.3423.104.123-.008.2386-.062.3244-.151.1195-.118.2596-.213.4133-.28.1472-.013.286-.074.3946-.174s.1808-.234.2054-.379z"/><path d="m98.306 134.3v.033c-.0667.267.18.353.3733.433.2667-.113.4134-.293.2934-.56-.04-.093-.2267-.206-.3067-.18l-.1533.08c.0215-.041.0351-.086.04-.133-.0124-.058-.0372-.112-.0726-.159-.0355-.047-.0808-.086-.1328-.114s-.1095-.044-.1684-.048c-.059-.003-.118.006-.1729.028-.0536.032-.0986.077-.1311.13s-.0516.114-.0556.176c.0064.073.0295.144.0677.206.0382.063.0903.115.1523.154.0451.013.0925.015.1387.007.0461-.008.0899-.026.128-.053z"/><path d="m96.28 137.18c.0411-.048.0717-.104.0897-.165s.0231-.124.0149-.187-.0294-.123-.0623-.177c-.033-.055-.0769-.101-.1289-.137-.1039-.063-.2253-.089-.3457-.076-.1205.013-.2332.066-.321.149-.0398.05-.0686.108-.0846.17-.0159.061-.0187.126-.008.189s.0346.123.0701.176.0818.098.1358.132c.0451.041.0984.072.1565.091s.1196.025.1802.018c.0607-.007.1192-.027.1715-.058.0523-.032.0973-.074.1318-.125z"/><path d="m97.6728 131.866c-.0431-.035-.0932-.061-.1471-.076-.0538-.015-.1102-.018-.1654-.01-.0553.008-.1082.028-.1555.057-.0472.03-.0876.07-.1186.116-.0456.044-.0811.098-.1041.157s-.033.122-.0292.186c.0038.063.0213.125.0512.181s.0715.104.1221.143c.0489.039.105.067.1651.084s.1229.022.1848.014c.062-.007.1219-.027.1762-.057.0544-.031.1021-.072.1405-.121.0677-.107.093-.236.0708-.36-.0222-.125-.0903-.237-.1908-.314z"/><path d="m96.9602 138.667c-.1415-.108-.32-.155-.4963-.132-.1762.024-.3359.117-.4437.258-.1079.142-.1551.32-.1314.497.0238.176.1166.335.258.443.1836.09.3921.115.5918.072.1997-.044.3788-.154.5082-.312.16-.206.0067-.64-.2866-.826z"/><path d="m95.6728 135.447c.0512.03.1082.05.1673.058.0591.007.1191.003.1765-.013.0573-.017.1108-.044.1571-.082.0464-.037.0846-.084.1124-.136.0392-.051.0672-.11.0822-.172.0149-.062.0164-.127.0045-.19-.012-.063-.0372-.123-.074-.176-.0368-.052-.0843-.096-.1394-.129-.1187-.047-.2498-.051-.3713-.012s-.2258.118-.2953.225c-.0514.109-.0614.232-.0283.347.0331.116.1072.215.2083.28z"/><path d="m93.8669 139.907c-.06.173-.14.606 0 .713s.4933-.187.5-.253c0-.2.08-.46-.1-.607s-.3667.06-.4.147z"/><path d="m95.7195 132.54c.0953-.012.187-.044.2695-.093.0826-.05.1542-.115.2105-.193.0769-.106.1303-.226.1568-.354.0264-.127.0252-.259-.0035-.386 1.3778.16 2.76.278 4.1462.353-.033.113-.053.227-.08.347-.284.05-.5487.177-.7662.366l-.1867-.1c-.1913-.039-.3905-.011-.5633.08-.1727.091-.3083.24-.3833.42-.0171.22.051.437.1901.607s.3384.28.5565.307c.1194.012.2399 0 .3544-.036.1145-.035.2208-.093.3123-.171l.0932.08c.38.3.713.14 1.033-.127.194.18.32.474.667.287.091-.046.164-.122.204-.216s.046-.199.016-.297c-.1-.367-.407-.3-.667-.267-.06-.16-.113-.307-.173-.453l.58-.254c.031-.162.047-.327.047-.493l1.62.04h.066c.067.01.134.01.2 0h1.467c-.01.124.014.248.071.359.056.111.143.204.249.268.095.067.204.114.319.136.114.022.233.02.346-.007.114-.028.22-.079.313-.15.092-.072.168-.163.222-.266.07-.116.102-.251.093-.387l1.147-.053c.135 0 .263-.053.358-.148s.149-.224.149-.359c0-.134-.054-.263-.149-.358s-.223-.148-.358-.148c-12.2998.12-24.5398-2.667-34.9132-9.414-6.2448-4.129-11.6176-9.446-15.8133-15.646-.8222-1.153-1.5618-2.363-2.2133-3.62-.0004-.03-.0073-.059-.0202-.085-.0129-.027-.0314-.05-.0544-.068-.0229-.019-.0496-.032-.0782-.039s-.0584-.007-.0872-.002l-.18-.313c-.2037-.049-.4187-.018-.6.087-.0582.055-.1044.121-.1361.194-.0316.073-.048.153-.048.232 0 .08.0164.159.048.233.0317.073.0779.139.1361.194.0329.055.0776.103.1309.139s.1139.06.1775.07c.0635.01.1286.006.1905-.011.0619-.018.1191-.049.1677-.091l.06.14c-.0543.059-.0887.134-.0983.215-.0095.08.0062.161.045.231.0314.06.0799.109.1393.141.0593.032.1269.046.194.039.4711 1.018.9986 2.008 1.58 2.967-.0116.074-.0063.15.0157.221.0219.072.0599.138.111.192.0571.061.1316.103.2133.12.1334.22.2734.447.4134.667-.1667.267-.1334.507.1533.787.1442.143.337.226.54.233.12.167.2333.347.36.513-.1959.007-.3838.08-.5333.207-.4134.327-.3534 1.013 0 1.127.3533.113.5333-.074.6.053.0666.127.08.447.3333.5s.46-.28.6133-.513c.3334.444.6734.889 1.02 1.333-.0249.084-.0312.172-.0185.258.0127.087.044.169.0919.242.0675.072.1492.129.2398.168s.1882.059.2868.059c.0667.073.1267.153.1934.233.42.5.86 1 1.3333 1.487-.1271.012-.2466.066-.34.153-.0772.131-.1074.284-.0855.435.0218.15.0943.288.2055.392.0544.067.123.121.2008.158.0779.037.163.056.2492.056s.1713-.019.2492-.056c.0778-.037.1464-.091.2008-.158.037-.04.0684-.085.0933-.134.8.84 1.6334 1.647 2.4867 2.434-.0308.064-.0468.135-.0468.206 0 .072.016.143.0468.207.0591.073.1331.133.2169.175.0839.043.1758.067.2698.072.0508-.009.0988-.029.14-.06.5133.453 1.04.893 1.5666 1.333-.0705.148-.107.31-.107.473 0 .164.0365.326.107.474.0717.188.2152.34.399.423.1837.082.3927.088.581.017.1592-.067.2975-.175.4-.314.36.28.7334.554 1.1.82.0557.07.1295.123.2134.154 1.2133.889 2.46 1.704 3.74 2.446-.0267.092-.0267.189 0 .28.0851.173.2298.31.4074.384.1775.075.3761.083.5592.023.0326-.017.0601-.042.08-.073.4934.28.9867.553 1.4867.813-.0086.092.0075.184.0467.267.2133.473.7533.78 1.1133.626.0889-.041.1684-.1.2333-.173.6667.333 1.3734.667 2.0734.96.0522.1.1368.178.2399.223.103.045.2183.053.3267.024.7267.313 1.46.613 2.2.893 0 .253.2734.3.52.26v-.053l.98.353c-.0123.071-.0123.143 0 .213.0213.056.0535.107.0948.15.0412.043.0906.077.1452.101.0547.023.1135.036.173.036.0596.001.1186-.011.1737-.033l.08-.04c.0606.146.1607.272.2891.363.1284.092.28.146.4375.157h.0601c-.0954.123-.1521.272-.1627.427-.0107.156.025.311.1026.446.053.12.125.23.2134.327-.0555.009-.1082.031-.1543.063-.0462.032-.0845.074-.1124.123-.033.073-.051.152-.0531.232-.002.08.0119.16.0411.234.0292.075.0731.143.1289.2.0559.058.1227.103.1965.134.1365.055.286.069.4303.041.1444-.028.2775-.098.383-.201.0784-.104.1208-.232.1208-.363s-.0424-.258-.1208-.363c.1698-.1.3002-.255.3694-.44.0691-.184.0729-.387.0106-.574-.0809-.184-.2187-.339-.3933-.44 1.2733.374 2.56.667 3.8533.974-.016.035-.0243.074-.0245.113-.0001.039.0079.078.0236.114.0157.035.0387.068.0676.094.0288.026.0629.046.1.059.1415.02.2851.02.4266 0 .0399-.077.0626-.161.0667-.247.56.116 1.12.222 1.68.32.0257.038.0572.072.0933.1.0734.071.1714.111.2734.111.1019 0 .1999-.04.2733-.111.3133.053.6266.107.94.147-.0691.105-.123.219-.16.34-.0358-.043-.0808-.077-.1316-.1-.0507-.023-.106-.035-.1617-.034-.0782.009-.1538.034-.2224.073-.0686.038-.1288.09-.1772.152s-.084.133-.1046.209c-.0207.076-.0261.155-.0158.233.0232.145.0878.28.186.388.0982.109.2257.187.3673.225.1309.015.2633-.012.3773-.078.1141-.066.2039-.167.256-.288.1357.062.2841.092.4333.086.013.023.0315.041.0537.054.0222.012.0474.019.073.019s.0508-.007.073-.019c.0223-.013.0408-.031.0537-.054z"/><path d="m102.887 138.153c-.07-.011-.142-.006-.209.016-.068.022-.129.06-.178.111-.073.093-.153.407-.04.507s.413-.047.513-.127c.035-.036.06-.08.075-.128.015-.047.018-.098.009-.147-.008-.049-.028-.096-.058-.136-.029-.04-.068-.073-.112-.096z"/><path d="m100.667 142.246c-.066.11-.086.24-.058.364.029.125.104.233.211.303.052.038.112.065.175.079.063.013.129.013.192-.001s.122-.041.174-.08.095-.088.126-.145c.058-.104.077-.226.051-.343s-.094-.22-.191-.29c-.051-.04-.11-.069-.173-.085s-.128-.019-.192-.008c-.064.01-.125.034-.179.07-.055.035-.101.082-.136.136z"/><path d="m101.747 135.18c-.071-.048-.151-.081-.236-.096-.084-.015-.171-.012-.254.008-.083.021-.161.059-.228.112s-.123.12-.162.196c-.1.138-.147.307-.133.477.013.169.086.329.206.45.166.067.348.083.523.045s.334-.128.457-.259c.059-.069.101-.151.124-.238.023-.088.027-.18.01-.269-.016-.09-.052-.174-.106-.248-.053-.073-.122-.134-.201-.178z"/><path d="m98.2799 140.314c-.0123.031-.0137.066-.0041.099.0097.032.0299.061.0575.081.0333 0 .14 0 .16-.054.02-.053 0-.126 0-.16-.0154-.013-.0337-.023-.0535-.029s-.0407-.007-.061-.004c-.0204.003-.0398.011-.0569.022-.0171.012-.0314.027-.042.045z"/><path d="m46.4134 101.506c.0533-.073.16-.226.06-.353s-.2933 0-.3133.053c-.0336.045-.0517.098-.0517.154 0 .055.0181.109.0517.153.0161.018.0359.032.058.042.0222.009.0461.014.0702.013.024 0 .0477-.006.0693-.017s.0406-.026.0558-.045z"/><path d="m100.667 137.6c-.024-.08-.063-.155-.114-.22-.061-.092-.147-.165-.248-.21-.102-.044-.213-.059-.3228-.042-.1093.017-.2115.064-.2946.137s-.1435.169-.1743.275c-.1095.303-.179.619-.2067.94.0128.188.0839.367.2034.513s.2814.251.4633.3c.2067.04.6667-.033.6667-.46-.347-.62.153-.707.027-1.233z"/><path d="m98.1798 143.026c-.057.046-.1033.104-.1355.17-.0323.065-.0497.137-.0511.21.0666.294.44.314.58.24.0429-.04.0764-.088.0984-.143.0219-.054.0316-.113.0284-.171-.0032-.059-.0192-.116-.0468-.167-.0277-.052-.0664-.097-.1134-.132-.0543-.032-.116-.049-.1791-.051-.0631-.001-.1254.014-.1809.044z"/><path d="m99.4929 135.713c-.1134-.16-.6667-.126-.72.12-.0171.099.0009.2.0509.286.0499.087.1286.153.2224.187.24.047.5666-.433.4467-.593z"/><path d="m83.9999 135.147c-.1867.106-.1467.26-.08.406.0666.147.3133.3.4733.16.0947-.108.1733-.229.2333-.36-.0261-.056-.0635-.106-.1097-.147s-.1003-.072-.159-.091c-.0587-.02-.1207-.027-.1823-.021-.0615.005-.1213.023-.1756.053z"/><path d="m85.3333 138.873c-.038.026-.0676.063-.0853.105-.0178.043-.0229.09-.0147.135.04.08.2066.1.3.087.0933-.013.2-.153.1533-.273s-.2533-.08-.3533-.054z"/><path d="m87.6796 133.554c-.1575-.06-.3284-.073-.4934-.04s-.3174.111-.44.226c-.1666.2-.3466.407-.1266.667s.2866.44.4333.667c.0035.059.0209.117.0508.168.03.052.0716.096.1216.128.0499.033.1068.053.1661.059.0592.007.1191-.001.1748-.022.1234-.04.2264-.126.2873-.241.061-.114.0751-.248.0394-.372-.0364-.165-.0364-.336 0-.5.0453-.131.0492-.272.011-.404-.0382-.133-.1166-.25-.2243-.336z"/><path d="m91.9199 138.246c-.0534.047-.1134.287 0 .36.1133.074.28-.066.3466-.126.0667-.06.0534-.194 0-.24-.053-.031-.1132-.046-.1742-.045-.061.002-.1206.019-.1724.051z"/><path d="m85.4664 131.287c-.0438.021-.0938.024-.1401.01-.0464-.015-.0856-.046-.1099-.088-.0244-.042-.032-.091-.0215-.139.0105-.047.0384-.089.0782-.117.1122-.193.1451-.423.0916-.641-.0534-.217-.189-.405-.3782-.525-.1177-.063-.2472-.101-.3804-.112-.1331-.01-.267.007-.393.052 0 0 0 0 0-.04-.0307-.068-.0754-.129-.1313-.178s-.1216-.086-.1928-.108c-.0712-.023-.1463-.029-.2203-.02s-.1452.034-.2089.072c-.0869.026-.1666.071-.2325.133s-.1162.139-.1467.224c-.0306.085-.0406.177-.0291.266.0114.09.044.176.095.251.0484.112.1226.21.2165.288.0938.077.2046.132.3234.158.0297.169.1228.319.26.42.2085.096.4424.122.6667.074-.0391.114-.0391.239 0 .353.043.103.1237.185.2254.229.1018.045.2168.049.3213.011.0623-.01.1215-.033.173-.07.0515-.036.0937-.084.1235-.14.0297-.056.0461-.117.0479-.18s-.0112-.126-.0378-.183z"/><path d="m103.767 134c.093-.118.14-.267.13-.417s-.075-.291-.184-.396c-.098-.075-.22-.111-.343-.101-.123.009-.238.064-.323.154-.075.144-.098.308-.067.467.032.159.116.302.24.406.088.057.194.076.296.055.103-.021.192-.081.251-.168z"/><path d="m82.6662 136.334c-.08 0-.26.2-.22.3s.2933.12.3666.086c.0499-.025.0903-.067.1154-.117.0252-.05.0338-.107.0246-.163-.0304-.045-.0745-.08-.126-.099-.0515-.02-.1077-.022-.1606-.007z"/><path d="m84.0925 133.333c-.0255-.055-.0621-.105-.1077-.146s-.0992-.072-.1573-.092c-.0582-.019-.1197-.026-.1808-.021s-.1203.023-.1742.053c-.1866.106-.1466.26-.08.406.0128.048.0371.092.0709.128.0339.036.0761.063.1229.079.0469.016.0969.02.1457.012.0487-.008.0947-.028.1339-.059.0951-.106.1717-.228.2266-.36z"/><path d="m90.1063 135.267c-.2531-.086-.5289-.076-.7749.029-.246.104-.4448.296-.5585.538-.1133.36.3334.566.5934.493s.1866 0 .3866.187c.0496.066.1157.118.1918.151.076.033.1594.045.2418.036.0823-.009.1608-.04.2274-.09.0667-.049.1192-.115.1524-.191.0781-.215.0744-.451-.0104-.664s-.2448-.387-.4496-.489z"/><path d="m90.6127 136.593c-.0434.028-.0789.067-.1034.112-.0245.046-.0371.096-.0366.148 0 .067.18.247.2933.2s.0867-.267.0667-.36c-.0189-.04-.0514-.072-.0917-.09-.0403-.019-.0858-.022-.1283-.01z"/><path d="m91.8195 132.574c-.0214-.056-.0535-.107-.0946-.15s-.0903-.078-.1448-.102-.1132-.037-.1728-.039c-.0595-.001-.1188.009-.1744.031-.1059.055-.1906.144-.2412.253-.0506.108-.0642.23-.0388.347.0224.055.0559.105.0982.147.0424.042.0929.075.1484.097.0555.021.1149.032.1745.03.0595-.002.1182-.016.1722-.041.0575-.017.1108-.046.1563-.085.0456-.038.0825-.087.1083-.141s.0399-.113.0414-.172c.0015-.06-.0096-.12-.0327-.175z"/><path d="m87.9461 131.294c-.0667.093.0466.313.1133.353.0215.017.0461.03.0724.037.0264.008.0539.01.0812.007.0272-.003.0536-.011.0776-.025.0239-.013.0451-.031.0621-.052.0171-.022.0298-.046.0374-.073.0075-.026.0098-.054.0067-.081s-.0115-.053-.0248-.077-.0311-.046-.0526-.063c-.06-.053-.32-.113-.3733-.026z"/><path d="m86.9726 136.853c-.2084.173-.3454.418-.3848.686-.0393.268.0217.541.1715.767.2467.28.6667 0 .7267-.253s.0866-.167.36-.233c.0832-.006.1635-.034.2324-.081s.1239-.111.1593-.187.0499-.159.042-.242-.038-.163-.0871-.23c-.1419-.179-.3435-.3-.5676-.341-.2242-.042-.4558-.001-.6524.114z"/><path d="m113.593 141.18c-.015.031-.018.065-.009.098.008.033.028.062.055.082.04 0 .147 0 .16-.054.014-.053.034-.126 0-.16-.015-.013-.032-.023-.052-.029-.019-.006-.039-.007-.059-.004s-.039.011-.055.022c-.017.012-.031.027-.04.045z"/><path d="m117.366 139.433c-.038.051-.066.108-.082.17-.015.062-.018.126-.007.188.011.063.034.123.069.176s.08.098.134.133c.038.04.085.071.138.09.052.019.108.025.164.017.055-.007.108-.027.154-.058.046-.032.084-.074.11-.122.039-.046.068-.1.085-.157.017-.058.022-.118.015-.178-.008-.059-.028-.117-.059-.168-.03-.051-.072-.096-.121-.131-.089-.064-.197-.095-.307-.087-.109.007-.213.052-.293.127z"/><path d="m115.266 133.626c-.065.082-.112.175-.139.276-.027.1-.032.205-.017.308.016.102.053.201.108.288.055.088.128.164.214.222.196.124.43.172.658.135.229-.037.436-.156.582-.335.102-.196.136-.421.096-.638-.039-.217-.151-.415-.316-.562-.096-.064-.205-.106-.319-.125-.113-.019-.23-.014-.342.015s-.216.081-.307.153c-.09.071-.165.161-.218.263z"/><path d="m114.813 141.113c-.053.04-.08.307 0 .353.08.047.28-.06.347-.126.067-.067 0-.227 0-.24-.054-.028-.115-.042-.175-.039-.061.002-.12.02-.172.052z"/><path d="m115.793 136.92c-.093.133 0 .4.247.54.029.028.064.049.102.06.039.012.08.014.119.006.04-.008.077-.025.108-.051.031-.025.055-.058.071-.095.107-.18.153-.373-.047-.533-.093-.059-.203-.083-.313-.07-.109.013-.21.064-.287.143z"/><path d="m113.913 135.567c-.061.079-.089.179-.078.278.012.1.061.191.138.255.133.14.62 0 .667-.153.01-.08.003-.161-.023-.237-.025-.077-.068-.146-.124-.203-.038-.043-.085-.076-.138-.096-.053-.021-.111-.028-.167-.022-.057.005-.111.025-.159.056s-.088.072-.116.122z"/><path d="m113.673 139.08c-.015.054-.014.112.002.167.017.054.048.102.091.139.087.047.267.134.32 0 .023-.051.031-.107.024-.162s-.029-.107-.064-.151c-.046-.02-.32-.093-.373.007z"/><path d="m123.547 130.726c-.067.094.046.314.113.354.021.017.046.029.072.037.027.008.054.01.082.007.027-.003.053-.012.077-.025s.045-.031.062-.053c.017-.021.03-.046.038-.072.007-.026.009-.054.006-.081s-.011-.054-.024-.078c-.014-.024-.032-.045-.053-.062-.06-.053-.307-.087-.373-.027z"/><path d="m113.426 139.887c-.124-.053-.262-.071-.396-.051s-.26.077-.364.164c-.055.107-.068.231-.034.346.033.116.11.214.214.274.052.033.11.054.17.062.061.008.122.004.181-.013.058-.017.112-.046.159-.086.046-.039.084-.088.11-.143.147-.22.14-.44-.04-.553z"/><path d="m104.366 137.473c-.266-.186-.573-.086-.813.267-.044.049-.077.106-.099.168s-.031.128-.027.194c.003.065.02.129.048.189.029.059.069.112.118.156.16.077.338.109.514.091.177-.018.345-.084.486-.191.05-.07.085-.151.1-.235.016-.085.013-.172-.008-.255-.022-.084-.062-.161-.117-.227-.055-.067-.124-.12-.202-.157z"/><path d="m127.293 130.04c-.042-.021-.09-.029-.136-.022-.047.007-.09.029-.124.062-.047.067 0 .22.087.293.086.074.233.094.313 0 .08-.093-.1-.28-.14-.333z"/><path d="m122.426 134.44c-.066.134-.14.514 0 .58.14.067.447-.073.567-.166.12-.094.107-.34-.087-.5-.028-.043-.069-.077-.117-.097-.047-.02-.1-.026-.151-.016-.051.009-.098.033-.135.068-.038.035-.064.081-.077.131z"/><path d="m120.033 129.287c-.107-.065-.233-.088-.356-.066-.122.022-.233.088-.31.186-.056.112-.071.24-.042.362.03.122.101.23.202.304.092.061.206.082.314.06.109-.023.204-.087.266-.18.04-.048.071-.103.09-.163.018-.06.024-.123.017-.186-.007-.062-.026-.123-.057-.177-.032-.055-.074-.103-.124-.14z"/><path d="m121.333 130.533c-.18-.087-.38-.113-.46.087s-.133.56 0 .666c.134.107.474-.18.58-.306.107-.127.114-.314-.12-.447z"/><path d="m120.24 139.14c-.054.107-.094.34 0 .413.066.03.139.042.212.035.072-.007.141-.033.201-.075.073-.066.147-.413.047-.486-.1-.074-.42.04-.46.113z"/><path d="m119.006 130.827c-.173-.053-.359-.045-.527.021-.168.067-.309.188-.399.345-.194.307-.08.607.306.84.125.062.267.081.403.053.137-.028.26-.1.351-.206.07-.172.095-.36.071-.544-.023-.185-.093-.36-.205-.509z"/><path d="m107.406 136.56c-.4-.287-.84-.307-1.02-.047-.066.166-.079.348-.037.521s.137.329.271.446c.148.056.309.066.463.027.154-.038.292-.122.397-.24.07-.109.101-.238.088-.366-.014-.129-.071-.249-.162-.341z"/><path d="m108.353 139.886c.04-.049.069-.105.086-.166s.021-.125.012-.187c-.008-.063-.03-.123-.063-.177s-.077-.1-.128-.136c-.106-.067-.232-.092-.355-.071s-.234.086-.312.184c-.057.111-.072.239-.044.361s.098.23.197.306c.047.033.1.057.157.07.056.012.114.013.171.003.057-.011.111-.033.159-.065s.089-.073.12-.122z"/><path d="m107.846 144.134c-.093.093-.173.626 0 .766.174.14.627-.213.74-.373.047-.098.062-.207.043-.314s-.071-.204-.149-.279c-.167-.134-.48.066-.634.2z"/><path d="m104.946 140.38c-.047-.042-.103-.073-.162-.092-.06-.019-.123-.025-.186-.019-.062.006-.123.025-.178.056-.054.03-.102.071-.141.121-.041.037-.073.083-.093.135-.021.051-.029.107-.023.162.005.055.023.108.052.155.03.047.07.086.118.115.043.048.096.085.155.11.059.024.124.035.188.031s.126-.022.182-.054c.056-.031.104-.075.141-.127.067-.088.099-.197.089-.307s-.06-.212-.142-.286z"/><path d="m108.593 141.14c-.18-.1-.373-.167-.533 0-.049.115-.058.245-.024.366s.108.227.211.3c.146 0 .3-.106.413-.226.031-.031.055-.068.069-.109.014-.042.017-.086.011-.129-.007-.043-.023-.083-.049-.119-.025-.035-.059-.064-.098-.083z"/><path d="m105.48 138.906c-.057.13-.064.276-.019.41.044.134.136.247.259.317.067.054.144.093.227.115.084.022.17.026.255.012s.166-.045.238-.092.133-.109.18-.182c.092-.119.138-.269.128-.42s-.074-.293-.182-.4c-.182-.087-.388-.111-.585-.068-.197.044-.373.152-.501.308z"/><path d="m104.86 142.906c-.07.103-.099.227-.083.35s.076.236.17.317c.107.067.237.09.361.063.124-.026.233-.098.305-.203.058-.111.074-.238.047-.36-.026-.122-.095-.23-.193-.307-.048-.035-.103-.06-.161-.073-.058-.012-.118-.011-.175.002-.058.013-.112.039-.159.076-.047.036-.085.082-.112.135z"/><path d="m110.84 137.546c-.115-.052-.245-.063-.366-.03-.122.033-.229.107-.301.21-.077.129-.109.28-.089.429.019.149.088.287.196.391.15.083.322.115.492.094.17-.022.329-.097.454-.214.107-.193-.086-.673-.386-.88z"/><path d="m111.373 143c-.021.061-.023.127-.005.189s.054.117.105.158c.047.02.098.027.149.019.05-.007.098-.027.137-.059.025-.044.038-.094.038-.144s-.013-.099-.038-.143c-.086-.053-.32-.14-.386-.02z"/><path d="m112.666 132.667c.098-.128.146-.286.137-.447-.01-.16-.078-.312-.19-.426-.227-.26-.507-.24-.787.18-.082.315-.144.636-.187.96-.007.056-.003.113.013.167s.043.105.079.148.082.078.132.103c.051.025.106.039.163.042.111.017.225-.011.316-.077s.153-.166.171-.277c.034-.13.085-.256.153-.373z"/><path d="m113.027 136.873c.113-.093.053-.527-.147-.667s-.293 0-.393.147c-.034.036-.059.08-.071.128-.013.047-.014.097-.003.146.011.048.034.092.067.13.033.037.074.065.12.083.147.073.314.126.427.033z"/><path d="m110.239 134.44c-.046-.533-.153-.72-.446-.747-.099-.014-.201.004-.288.054s-.155.127-.192.22c-.063.108-.084.236-.06.358.025.123.093.233.193.308.313.16.553-.033.793-.193z"/><path d="m109.559 131.587c.117.094.26.15.409.158.15.008.298-.031.424-.111.088-.137.141-.293.154-.454.013-.162-.015-.325-.08-.473-.667.073-1.334.147-2 .2.046.167.16.313.313.3.153-.022.309.003.448.07.14.068.255.176.332.31z"/><path d="m110.3 135.6c-.049-.055-.11-.097-.178-.123-.069-.025-.143-.034-.215-.024-.086.04-.162.096-.225.166s-.111.152-.142.241c-.067.353.26.38.527.5.246-.24.426-.474.233-.76z"/><path d="m92.9794 135.633c-.2133.073-.14.347-.08.42s.4467.433.6067.373.1-.526.04-.566-.3467-.3-.5667-.227z"/><path d="m56.6658 111.58c-.24.1-.1533.667.18.947.0833.074.1913.116.3033.116.1121 0 .2201-.042.3034-.116.2466-.2.38-.614.2333-.807-.28-.387-.76-.247-1.02-.14z"/><path d="m56.5793 115.62c-.1379.119-.2276.285-.2522.465-.0245.181.0177.364.1189.515.1371.12.3104.189.4919.198.1815.008.3605-.045.5081-.151.2-.167.1533-.667-.0733-.933-.045-.06-.1016-.11-.1664-.147-.0647-.037-.1363-.06-.2104-.069s-.1492-.002-.2208.019-.1382.056-.1958.103z"/><path d="m55.8463 114.487c-.1666-.12-.5266 0-.6666.1-.0356.037-.0629.081-.08.129-.0171.049-.0237.1-.0193.151.0045.051.0198.101.0449.146.0252.045.0596.083.101.114.0311.034.0699.06.1133.076.0433.016.0899.022.1357.016.0459-.006.0896-.023.1275-.05.0379-.026.0689-.061.0902-.102.1133-.12.3067-.454.1533-.58z"/><path d="m58.4726 119.6c-.12-.167-.38 0-.4533.053s-.1733.24-.0533.367c.0515.042.1163.066.1833.066s.1318-.024.1833-.066c.08-.02.26-.247.14-.42z"/><path d="m55.9058 122c-.0246.042-.0376.089-.0376.137s.013.095.0376.137c.0457.026.0974.04.1501.04.0526 0 .1043-.014.15-.04.0533-.054.1666-.234 0-.36-.1667-.127-.2067.046-.3001.086z"/><path d="m55.6868 117.253c-.016.057-.016.117 0 .174.0408.015.0858.015.1266 0 0-.054.0867-.127.0467-.18-.04-.054-.1133.006-.1733.006z"/><path d="m60.5529 116.82c-.0457.064-.0776.137-.0937.214-.016.077-.0159.156.0003.233.0534.12.4667.26.6134.12.0785-.079.1227-.185.1227-.297 0-.111-.0442-.218-.1227-.296-.0317-.04-.0725-.072-.1189-.093s-.0972-.03-.148-.028c-.0509.003-.1004.018-.1444.043-.044.026-.0813.061-.1087.104z"/><path d="m61.8929 115.874c.0501-.043.0889-.098.1131-.16.0242-.061.033-.127.0258-.193s-.0303-.129-.0673-.183c-.037-.055-.0867-.1-.1449-.131-.1267-.047-.36.16-.5333.273-.0238.011-.045.027-.0622.047s-.03.044-.0375.069c-.0076.025-.0097.051-.0063.077s.0122.052.026.074c.0824.097.1957.162.3208.185s.2542.003.3658-.058z"/><path d="m58.7595 120.666c-.2.047-.4467.507-.3.667.1466.16.4333.36.6667 0 .2333-.36-.1667-.74-.3667-.667z"/><path d="m61.6331 123.42c-.0866.093-.2666.38-.0666.573.2.194.4266-.033.4933-.113.0327-.063.0498-.132.0498-.203s-.0171-.141-.0498-.204c-.0222-.034-.0517-.063-.0864-.085s-.0738-.036-.1145-.041-.082-.001-.121.011c-.039.013-.0748.034-.1048.062z"/><path d="m61.7993 121.613c.0734 0 .18-.173.1334-.28-.0467-.107-.2067-.133-.2867-.107-.08.027-.2533.2-.2133.3s.2933.12.3666.087z"/><path d="m62.5402 120.5c-.0241.011-.0432.03-.0533.054-.2067 0-.6667.366-.5334.6.0502.097.1338.173.2355.213.1017.041.2146.043.3179.007.1466-.074.1866-.34.1533-.547h.0733c.0534 0 .2067-.16.16-.28-.0466-.12-.2666-.073-.3533-.047z"/><path d="m54.6666 117.267c-.2.146-.0533.513-.08.726l.1267.074c.0899.022.1848.015.2702-.021s.1567-.099.2031-.179c.0942-.1.1508-.23.16-.367-.22-.067-.4933-.373-.68-.233z"/><path d="m60.1401 118c-.1667.06-.16.44 0 .56.1191.054.2493.079.38.073.2533-.22.3-.486.1333-.573-.0772-.044-.1624-.072-.2506-.082-.0881-.01-.1775-.003-.2627.022z"/><path d="m60.0934 115.333c-.16-.667-.7866-.367-.7333-.8s.4267-.2.46-.82-.48-1.013-.86-.8-.3533.607-.4533.627-.5667 0-.58.353c0 .733.6666.587.6666.807s-.1.666-.04.98c.0867.466.42.84.9.666.1114-.019.2175-.061.3115-.124.0939-.063.1737-.144.234-.24.0603-.095.0999-.203.1162-.314.0163-.112.0089-.226-.0217-.335z"/><path d="m48.8661 107.333c-.039.032-.0705.072-.0921.118-.0216.045-.0328.095-.0328.146 0 .05.0112.1.0328.145.0216.046.0531.086.0921.118.0905.12.2215.203.3687.234.1472.03.3004.006.4313-.067.0999-.11.1637-.247.1828-.394.0191-.146-.0075-.295-.0761-.426-.1933-.2-.6133-.134-.9067.126z"/><path d="m55.3861 113.16c.0864-.134.1271-.293.1163-.453s-.0726-.312-.1763-.433c-.0938-.074-.2079-.117-.327-.125-.1192-.007-.2376.023-.3396.085-.3334.306-.4334.733-.22.946.1401.096.3067.146.4765.142.1698-.003.3342-.06.4701-.162z"/><path d="m47.7398 105.647c-.0804.113-.1236.248-.1236.387 0 .138.0432.273.1236.386.0425.046.094.083.1514.108.0573.025.1193.038.1819.038.0627 0 .1246-.013.182-.038s.1089-.062.1514-.108c.0879-.085.1428-.199.155-.321s-.0191-.244-.0884-.345c-.0979-.089-.2199-.146-.3504-.165-.1306-.019-.2638.001-.3829.058z"/><path d="m51.2534 103.26c-.1743-.121-.3812-.186-.5933-.186-.2467-.087-.8.046-.8867.26-.0867.213.1467.373.3733.46.0416.133.1148.254.2134.353.26.253.5733.213.8866-.113.0814-.113.1258-.247.127-.386s-.0409-.274-.1203-.388z"/><path d="m48.0333 112.7c-.0261.037-.0401.082-.0401.127s.014.09.0401.127c.06.066.22 0 .3067 0 .0866 0 .14-.214.06-.314s-.2934.027-.3667.06z"/><path d="m47.6396 99.5865c-.1533-.28-.2333-.6333-.5933-.78l-.4267.1467c-.108-.0743-.2273-.1306-.3533-.1667.0032-.0154.0032-.0313 0-.0467-.0534-.1266-.2667-.0933-.3067-.06l-.0867.0734c-.0613.0146-.117.0471-.16.0933-.06.0746-.0928.1675-.0928.2633s.0328.1888.0928.2634c.2067.3266.36.3666.7934.2266.2524.1436.5128.2727.78.3867.0534.0135.1096.0122.1623-.0041.0527-.0162.0999-.0467.1364-.088.0366-.0414.061-.092.0706-.1463s.0041-.1102-.016-.1616z"/><path d="m80.9267 132.833c-.0787.033-.1498.082-.2088.143-.059.062-.1046.135-.134.215s-.042.166-.0369.251.0277.168.0664.244c.1.274.4467.36.8267.207.0634-.014.1228-.042.1742-.082s.0935-.091.1234-.148c.0298-.058.0467-.122.0494-.187s-.0088-.13-.0337-.19c-.0872-.14-.209-.255-.3539-.335-.1448-.079-.3076-.12-.4728-.118z"/><path d="m49.9599 105.247c-.1334-.147-.28-.12-.4334.04 0 .06-.0333.166 0 .213.1212.178.1798.392.1667.607 0 .1.22.246.3467.26.0773-.003.1532-.022.2231-.055.0699-.034.1323-.081.1835-.139.021-.062.0283-.128.0214-.194s-.0279-.129-.0614-.186c-.1339-.194-.2833-.376-.4466-.546z"/><path d="m53.0733 113.213c-.1267-.166-.32-.073-.46.06-.14.134-.1667.434 0 .514.1666.08.4266.253.5733.04.1467-.214-.0133-.494-.1133-.614z"/><path d="m53.6466 114.246c-.1366-.056-.29-.056-.4266 0-.1667.094-.14.467.0466.567.1119.036.2301.047.3467.033.2133-.233.2133-.513.0333-.6z"/><path d="m54.3264 109.667c-.1737-.153-.3939-.243-.6251-.255-.2311-.013-.4596.054-.6483.188-.3533.28-.32.833.0667 1.287.0631.077.1411.141.2293.188.0882.046.1849.075.2843.084.0995.008.1997-.003.2947-.034.0949-.03.1828-.08.2584-.145.1802-.164.2941-.388.3199-.63s-.0382-.485-.1799-.683z"/><path d="m51.0131 117.487c-.0511.039-.0926.089-.1211.147s-.0434.122-.0434.186c0 .065.0149.128.0434.186s.07.108.1211.147c.034.043.0766.079.125.104.0484.026.1016.041.1562.045.0546.003.1093-.004.1608-.023.0514-.019.0985-.048.138-.086.2067-.206.28-.62.1333-.766-.1123-.059-.239-.085-.3654-.074-.1263.011-.247.057-.3479.134z"/><path d="m51.1599 114c-.0927.092-.1502.214-.1624.344-.0121.131.0217.261.0957.369.1533.167.54.127.8534-.153 0-.087 0-.274-.04-.387-.0781-.12-.1993-.204-.3385-.237-.1392-.032-.2855-.009-.4082.064z"/><path d="m50.9467 111.246c-.0565.071-.0986.151-.124.237-.0253.087-.0335.177-.0239.267.0194.18.1098.346.2512.46.1415.114.3224.167.5031.148.1806-.02.3462-.11.4602-.252.1213-.103.1984-.249.2158-.408.0173-.158-.0265-.317-.1224-.445-.1632-.134-.3676-.208-.5788-.209-.2113-.001-.4165.07-.5812.202z"/><path d="m47.4332 113.673c-.0666.047-.18.28-.1067.36.0734.08.3201 0 .3734-.04.021-.017.0383-.039.0511-.063.0127-.024.0205-.051.023-.078.0024-.027-.0005-.055-.0086-.081s-.0213-.05-.0389-.071c-.0175-.021-.0389-.038-.0632-.051-.0242-.013-.0507-.021-.0779-.023-.0272-.003-.0547 0-.0808.008-.0261.009-.0504.022-.0714.039z"/><path d="m74 131.293c-.0547 0-.1082.016-.1551.044s-.0854.068-.1116.116c-.04.1.1067.227.1934.253.0866.027.2933-.04.3266-.166.0334-.127-.1666-.247-.2533-.247z"/><path d="m76.1132 132.42c-.0666-.194-.5133-.16-.7533.1s.0667.62.2333.666c.1667.047.5867-.58.52-.766z"/><path d="m76.1534 129.62c-.1-.18-.2467-.293-.44-.227-.4988.141-.9459.423-1.2867.814-.0398.071-.069.147-.0867.226-.0275.105-.0247.216.0082.319.0329.104.0945.196.1776.266s.1843.114.2918.129c.1076.014.2171-.002.3158-.047.3071-.105.5986-.25.8666-.433.131-.137.2162-.312.2437-.499.0274-.188-.0041-.379-.0903-.548z"/><path d="m72.8993 133.067c.017.107.071.205.1525.277.0816.072.1856.113.2942.116.2733-.04.4333-.667.2733-.773-.16-.107-.7466.113-.72.38z"/><path d="m71.8465 135.1c-.1067.06-.3733.273-.2467.52.1267.247.4134.107.5067.053.0551-.044.0986-.102.1264-.167.0279-.065.0395-.136.0336-.206-.0081-.045-.0268-.087-.0544-.123s-.0634-.065-.1044-.085c-.041-.019-.0861-.029-.1315-.027-.0454.001-.0899.013-.1297.035z"/><path d="m73.333 129.22c.0985-.051.1737-.137.2097-.241.0361-.105.0302-.219-.0163-.319-.0172-.048-.0453-.091-.082-.126-.0368-.035-.0812-.061-.1297-.076s-.0998-.019-.1499-.01c-.0501.008-.0975.028-.1385.058-.0628.047-.1154.105-.1544.173s-.0636.143-.0722.221c-.02.133.32.433.5333.32z"/><path d="m72.2268 129.486c-.18 0-.3.367-.1733.54.1267.174.26.154.3333.194.3134-.127.4467-.36.32-.5-.125-.136-.2963-.219-.48-.234z"/><path d="m75.7396 138.373c-.0183.058-.0219.12-.0102.179.0116.059.0381.115.0769.161.0636.038.1361.058.21.058s.1464-.02.21-.058c.0867-.106-.0733-.386-.1533-.426-.0584-.015-.1196-.015-.178 0-.0583.015-.1118.045-.1554.086z"/><path d="m79.5059 134.627c.1092-.026.204-.093.2639-.188s.0801-.209.0561-.319c-.0405-.144-.1364-.266-.2667-.34-.24-.113-.4133 0-.6666.447.1466.213.2466.513.6133.4z"/><path d="m72.1465 127.72c.1129.016.2278.008.3373-.023.1095-.032.211-.087.2979-.16.087-.074.1573-.165.2064-.268s.0758-.215.0784-.329c.06-.667-.6266-.607-.4333-1s.4733-.047.7067-.62c.2333-.573-.12-1.113-.5534-1.04-.4333.073-.5266.453-.6266.447-.1-.007-.54-.187-.6667.14-.2667.666.4067.76.3933.986-.0133.227-.3133.587-.3666.92-.0734.467.1266.927.6266.947z"/><path d="m79.6394 138.38c-.1885.111-.3338.283-.4124.488-.0787.204-.086.429-.0209.638.0835.206.2421.373.4437.467s.4313.108.6429.04c.1894-.148.3353-.345.4221-.569s.1115-.467.0713-.704c-.1099-.195-.2894-.34-.5023-.407-.2129-.066-.4432-.05-.6444.047z"/><path d="m79.3329 132.474c.1031-.059.1814-.153.2201-.265.0388-.112.0353-.234-.0097-.344-.045-.109-.1284-.199-.2347-.251-.1062-.053-.228-.065-.3424-.034-.1105.046-.1998.132-.2504.241-.0506.108-.0587.232-.0229.346.0493.122.1422.221.2606.277.1184.057.2537.068.3794.03z"/><path d="m78.0794 130.793c-.0758-.014-.1538-.009-.2272.015-.0733.023-.1397.064-.1931.12-.0535.055-.0924.123-.1133.197s-.0231.153-.0064.228c.0303.089.0802.169.146.236s.1457.119.234.151c.0845.008.1698-.003.2488-.035.0789-.031.1491-.081.2045-.145.0385-.069.0614-.145.0672-.223.0057-.078-.0058-.156-.0338-.23-.028-.073-.0718-.139-.1283-.193-.0564-.055-.1242-.096-.1984-.121z"/><path d="m76.0395 134.98c-.0861.029-.1653.076-.2329.136-.0676.061-.1221.134-.1601.217-.0381.082-.059.172-.0613.262-.0024.091.0138.181.0476.265.1067.216.2837.389.5018.491s.4643.126.6982.069c.1708-.091.3012-.243.3654-.425.0642-.183.0575-.383-.0187-.561-.042-.107-.1055-.205-.1867-.286-.0811-.082-.1781-.146-.285-.188-.1069-.043-.2213-.063-.3363-.059-.115.003-.228.03-.332.079z"/><path d="m63.7734 126c-.0552.024-.1049.058-.1457.102-.0407.044-.0716.096-.0905.153-.019.057-.0255.117-.0193.177.0063.059.0252.117.0555.168.0173.052.0455.1.0827.14.0371.041.0824.073.1329.094.0504.021.1048.032.1596.031.0548-.002.1087-.014.1581-.038.2667-.12.4734-.493.38-.667-.0926-.089-.2082-.152-.3341-.18s-.2571-.021-.3792.02z"/><path d="m66.8995 131.9c-.0321.036-.0549.079-.0665.125-.0116.047-.0117.095-.0002.142.0347.041.0793.072.1294.091.0502.018.1044.024.1573.016.0666 0 .2333-.167.14-.334-.0934-.166-.28-.08-.36-.04z"/><path d="m67.2792 126.993c-.2334.073-.22.46-.3134.667.0331.036.0643.074.0934.113.0744.055.1643.084.2566.084.0924 0 .1823-.029.2567-.084.1218-.065.2163-.171.2667-.3-.16-.14-.32-.553-.56-.48z"/><path d="m66.9465 125.366c.0467-.126-.1933-.333-.2866-.346-.0608.006-.1187.029-.1678.065-.0492.036-.0879.085-.1122.141.0026.061.0205.121.0519.173s.0755.096.1281.127c.0729.011.1474.002.2155-.026s.1272-.075.1711-.134z"/><path d="m64.7734 119.48c-.1267-.153-.54 0-.6667.347s.2667.567.4467.573c.18.007.3466-.76.22-.92z"/><path d="m66.5994 123.134c.12.113.28.246.44.133s.14-.467.0867-.62c-.0096-.035-.0291-.067-.0563-.092-.0271-.025-.0608-.042-.097-.048.0019-.194-.0695-.382-.2-.527-.0749-.087-.1669-.158-.2704-.209-.1036-.05-.2163-.079-.3314-.084-.115-.005-.2298.014-.3373.055-.1075.042-.2054.104-.2876.185-.1295.104-.2157.252-.2416.416s.0104.332.1016.471c-.0514-.032-.1087-.052-.1684-.06s-.1204-.004-.1783.013c-.1215.057-.2187.155-.2737.278-.0551.122-.0644.26-.0263.389.0934.2.4667.293.86.133 0-.087.12-.253.0867-.38-.014-.05-.0366-.097-.0667-.14.1443.083.3049.134.4706.149.1657.014.3326-.007.4894-.062z"/><path d="m64.0797 117.88c.0836-.173.1106-.367.077-.557-.0335-.189-.1257-.362-.2636-.496-.0579-.073-.1414-.12-.2333-.132-.0919-.013-.1851.011-.2601.065-.429.293-.7591.709-.9466 1.194-.013.079-.013.16 0 .24.0062.11.044.217.1089.307s.1542.159.2573.2c.1031.04.2157.051.3245.029.1088-.021.2092-.073.2893-.15.2437-.205.461-.44.6466-.7z"/><path d="m69.6459 126.067c-.1675.07-.3044.198-.3862.361-.0818.162-.1032.349-.0604.525.0892.158.2295.282.3979.349.1684.068.3548.077.5287.025.24-.094.3667-.58.24-.907-.0232-.071-.0605-.137-.1095-.193-.049-.057-.1088-.103-.1759-.136-.0671-.032-.1402-.052-.2148-.056s-.1493.007-.2198.032z"/><path d="m70.0529 131.526c-.2067 0-.5867.334-.5134.56.0734.227.2934.487.6667.254.3733-.234.0533-.787-.1533-.814z"/><path d="m68.1934 127.474s.1066.08.12.066c.0133-.013.1199-.093.1-.153-.02-.06-.0934-.047-.1467-.067-.034.046-.0589.098-.0733.154z"/><path d="m69.8466 130.82c.0933 0 .3333-.167.28-.367-.0534-.2-.36-.14-.4467-.1s-.24.167-.1733.327c.0302.06.0813.108.1438.133.0625.026.1321.028.1962.007z"/><path d="m71.2662 123.913c-.1679-.146-.3852-.224-.6079-.216-.2227.007-.4343.099-.5921.256-.1415.167-.2225.378-.2297.597-.0073.219.0596.434.1897.61.1468.167.3516.272.573.295.2215.022.4431-.041.6203-.175.1362-.2.2129-.435.2211-.677.0083-.242-.0522-.481-.1744-.69z"/><path d="m69.3329 123.333c.127-.098.2185-.236.2606-.391s.0325-.319-.0273-.469c-.0628-.103-.1566-.183-.2679-.23s-.2345-.057-.3521-.03c-.4133.18-.6667.547-.5133.827.1006.137.2415.238.4029.291s.3352.053.4971.002z"/><path d="m69.3327 124.76c-.1133-.173-.4933-.167-.6333-.113-.0458.022-.0862.054-.1183.094-.0322.039-.0554.085-.0681.135-.0126.049-.0145.101-.0053.151.0091.05.029.098.0583.14.0178.042.0456.08.0811.11.0355.029.0776.05.1228.06s.0921.008.1367-.004.0856-.035.1194-.066c.1134-.087.4134-.334.3067-.507z"/><path d="m174.579 74.8867h-8.253v8.2534h8.253z"/><path d="m193.666 93.9736h-8.253v8.2534h8.253z"/><path d="m174.579 93.9736h-8.253v8.2534h8.253z"/><path d="m155.499 74.8867h-8.253v8.2534h8.253z"/><path d="m155.499 93.9736h-8.253v8.2534h8.253z"/><path d="m134.393 74.8867h-8.253v8.2534h8.253z"/></g><path d="m105.439 32.8135c-8.4385 0-16.6877 2.5023-23.7042 7.1906s-12.4852 11.3519-15.7145 19.1482c-3.2294 7.7963-4.0743 16.3752-2.428 24.6517s5.7099 15.879 11.677 21.846c5.967 5.967 13.5695 10.031 21.846 11.677 8.2767 1.646 16.8557.801 24.6517-2.428s14.46-8.698 19.148-15.7145 7.191-15.2657 7.191-23.7044c0-11.3159-4.495-22.1683-12.497-30.1698-8.002-8.0016-18.854-12.4968-30.17-12.4968zm0 69.3935c-5.291 0-10.4642-1.569-14.8639-4.5094-4.3997-2.94-7.8287-7.1187-9.8534-12.0076-2.0248-4.8889-2.5542-10.2685-1.5213-15.4583 1.0328-5.1898 3.5815-9.9568 7.3236-13.698 3.7422-3.7413 8.5098-6.2888 13.7-7.3203 5.19-1.0315 10.569-.5008 15.458 1.5251 4.888 2.0259 9.066 5.456 12.005 9.8565 2.939 4.4004 4.507 9.5735 4.505 14.8651 0 3.513-.692 6.9916-2.036 10.2371-1.345 3.2455-3.315 6.1943-5.8 8.678-2.484 2.4838-5.433 4.4538-8.679 5.7978s-6.725 2.035-10.238 2.034z" fill="#1ba9f5"/><path d="m133.935 93.1051-10.465 10.4649 27.143 27.144 10.465-10.465z" fill="#0a89db"/><path d="m73.0864 74.8332c.0636.0134.1293.0139.193.0012.0638-.0127.1243-.0382.178-.075.0536-.0368.0992-.0841.1339-.139.0348-.0549.0581-.1164.0684-.1806.0109-.1304-.0228-.2607-.0954-.3697-.0726-.1089-.18-.1901-.3046-.2303-.1378.0177-.268.0732-.3762.1602-.1082.0871-.1903.2024-.2371.3332-.0267.16.24.46.44.5z" fill="#0d90e0"/><path d="m83.3734 59.4865c.1466.0533.2333-.18.2266-.2267-.0066-.0466-.0333-.22-.1733-.2466-.0225-.0072-.0464-.0092-.0697-.0056-.0234.0035-.0456.0124-.065.026-.0194.0135-.0353.0314-.0467.0521-.0113.0208-.0177.0439-.0186.0675-.0067.0933-.0067.28.1467.3333z" fill="#0d90e0"/><path d="m84.3733 58.993c.1533-.2.3067-.4066.4733-.6-.0408-.0455-.0908-.0818-.1466-.1066-.28-.0867-.6667.0866-.6667.32-.0333.12.1467.2733.34.3866z" fill="#0d90e0"/><path d="m69.1263 76.9668c.0618.0065.1243.0005.1838-.0176s.1148-.0479.1626-.0876c.0479-.0398.0873-.0887.1159-.1439.0287-.0552.046-.1155.051-.1775.0533-.28-.14-.6667-.38-.6667-.1454-.0263-.2954.0039-.4193.0845-.1239.0805-.2124.2053-.2474.3488-.0168.0796-.0157.1618.0032.2409.019.079.0553.1528.1064.2161.0511.0632.1157.1142.189.1494.0733.0351.1535.0534.2348.0536z" fill="#0d90e0"/><path d="m69.4525 68.9864.22-.2266h-.7067c.1434.1107.3096.1882.4867.2266z" fill="#0d90e0"/><path d="m65.3335 70.3871c.0622.0203.1282.0261.1929.0168.0647-.0092.1265-.0332.1805-.0701.1466-.1157.2535-.2741.3061-.4532.0526-.1792.0481-.3703-.0128-.5468-.0255-.1082-.0917-.2024-.1847-.2632-.0931-.0608-.206-.0835-.3153-.0635-.1643.0143-.3192.083-.44.1954-.1207.1123-.2005.2618-.2267.4246-.0033.06-.0033.1201 0 .18v.04c.15.1948.3174.3755.5.54z" fill="#0d90e0"/><path d="m67.186 68.7598h-.4067.0533c.115.0363.2384.0363.3534 0z" fill="#0d90e0"/><path d="m63.5527 77.1999c.0099.1677.0577.331.1396.4776.082.1466.1961.2728.3338.3691.28.08.6133-.2.7066-.6.0934-.4-.06-.5734-.5-.6667-.0722-.0178-.1473-.021-.2208-.0094-.0735.0115-.144.0376-.2073.0767s-.1182.0905-.1614.151c-.0433.0606-.074.1291-.0905.2017z" fill="#0d90e0"/><path d="m68.8467 73.7733c.1219-.1799.1763-.3972.1533-.6133-.08-.2067-.3333-.34-.5333-.5334.1066-.1866.2333-.42 0-.6666-.62 0-.6667.1266-.3867.6666-.1206.224-.1996.4679-.2333.72.0432.2332.1573.4474.3267.6134.1733.2133.4933.12.6733-.1867z" fill="#0d90e0"/><path d="m64.3734 72.4263c.1515.1886.3536.3302.5827.4081.229.078.4756.089.7107.0319.3266-.1467.2533-.5867.34-.6667.0866-.08.5933.2334.9866 0 .179-.1336.3117-.3198.3798-.5325.0682-.2127.0682-.4414.0002-.6541-.0824-.2574-.2617-.4726-.5-.6-.1574-.0702-.3323-.0919-.5021-.0622s-.3269.1094-.4512.2288c-.2067.26-.2133.7667-.3333.82-.12.0534-.6-.2866-.98-.0533-.0885.0536-.1652.1246-.2253.2088-.06.0842-.1023.1798-.1241.2809-.0219.1011-.0228.2056-.0029.3071.02.1015.0604.1979.1189.2832z" fill="#0d90e0"/><path d="m70.046 75.5604c.0843-.0022.1672-.0224.243-.0592.0759-.0368.1431-.0893.197-.1541.1934-.34-.0466-.5533-.32-.7533-.26.1066-.5533.2066-.5466.5466-.0019.0563.0078.1123.0287.1646.0208.0522.0523.0996.0924.1391.0401.0394.088.0702.1406.0902s.1087.0289.1649.0261z" fill="#0d90e0"/><path d="m77.246 56.2935c.2733.0734.5933-.22.6666-.6133.0139-.0479.0173-.0982.01-.1475-.0074-.0493-.0253-.0964-.0525-.1382-.0272-.0417-.0631-.0772-.1052-.1038s-.0895-.0438-.1389-.0505c-.32-.0933-.7067.0733-.76.3133-.02.1474.0057.2974.0736.4298.068.1323.1749.2406.3064.3102z" fill="#0d90e0"/><path d="m75.2391 58.6202c-.08.16.1.5467.3067.6667.0451.0212.0942.0322.144.0324.0498.0001.099-.0107.1441-.0317.0452-.021.0852-.0516.1172-.0898.032-.0381.0552-.0828.068-.1309.14-.3467.1267-.62-.04-.7067-.1353-.0266-.2753-.0165-.4054.0292s-.2456.1254-.3346.2308z" fill="#0d90e0"/><path d="m76.6063 61.3331h.52c.1724-.0978.3011-.2575.36-.4467.0049-.1202-.0305-.2386-.1005-.3365-.0701-.0978-.1708-.1694-.2862-.2035-.46-.0733-.8.0667-.8467.3533.0021.1266.0354.2506.0971.3611.0616.1105.1497.2041.2563.2723z" fill="#0d90e0"/><path d="m80.7929 56.1531c.0521.0163.1069.0219.1612.0166.0543-.0054.107-.0216.1548-.0477.0479-.0261.0901-.0616.124-.1044.0339-.0427.0588-.0919.0733-.1445.0106-.0487.0112-.099.0017-.1479-.0095-.049-.0289-.0954-.057-.1366s-.0642-.0762-.1063-.103c-.0421-.0267-.0892-.0446-.1384-.0525-.0433-.013-.0888-.0171-.1338-.012s-.0884.0193-.1277.0418c-.0393.0224-.0737.0526-.1009.0888-.0272.0361-.0468.0775-.0576.1214-.0203.0448-.031.0934-.0315.1426s.0093.0979.0288.1431c.0194.0452.0481.0858.0842.1193.036.0334.0787.059.1252.075z" fill="#0d90e0"/><path d="m80.0793 57.3334c-.3333-.0734-.5667.1066-.6667.52-.1.4133.12.5666.5267.6666.1142.0375.2384.0301.3474-.0207.1089-.0508.1945-.1411.2393-.2526.0169-.1786-.0158-.3584-.0946-.5196-.0789-.1612-.2007-.2975-.3521-.3937z" fill="#0d90e0"/><path d="m80.2996 53.3797c.1133-.0733.0866-.3866.1-.5933.0047-.0255.0041-.0518-.0019-.0771-.006-.0252-.0172-.049-.0329-.0697s-.0355-.0379-.0582-.0505-.0478-.0203-.0737-.0227c-.1275.0049-.2493.0542-.3443.1394-.095.0853-.1571.201-.1757.3273.003.0676.0227.1334.0576.1914.0348.058.0835.1065.1418.1409.0582.0344.1242.0538.1918.0563s.1348-.0119.1955-.042z" fill="#0d90e0"/><path d="m79.1662 56.8003c.1324.0166.2667-.007.3855-.0676.1189-.0606.2168-.1555.2811-.2724.0081-.1445-.0312-.2878-.1118-.408-.0805-.1203-.1981-.2111-.3348-.2586-.1201-.0152-.2414.0176-.3375.0912s-.1593.1822-.1759.3021c-.0328.1204-.0213.2486.0326.3613.0538.1126.1464.202.2608.252z" fill="#0d90e0"/><path d="m77.4933 57.1604c-.0614.0449-.113.1017-.152.167-.0389.0653-.0643.1377-.0747.213 0 .0934.14.2467.2467.2934.2158.0863.4389.1532.6666.2.2.0466.3067-.0534.34-.2734-.0333-.0466-.06-.1533-.12-.1733-.2016-.0804-.3705-.2259-.48-.4133-.0649-.036-.1375-.056-.2117-.0583-.0742-.0024-.1479.0131-.2149.0449z" fill="#0d90e0"/><path d="m73.3328 60.1803c-.0467-.28-.1134-.5533-.4534-.5133s-.3733.3-.34.5667c.0514.0567.1149.101.1858.1297.071.0287.1475.0409.2238.0357.0764-.0051.1505-.0274.217-.0653s.1235-.0904.1668-.1535z" fill="#0d90e0"/><path d="m76.9463 75.1269c.1173.0247.2395.0075.3454-.0485.1059-.0561.1889-.1474.2346-.2581.0128-.1305-.011-.262-.0687-.3797-.0577-.1178-.147-.2171-.258-.287-.1281-.0081-.2557.0233-.3654.0901s-.1962.1657-.2479.2832c-.02.0631-.0264.1297-.0187.1954.0077.0658.0293.1291.0634.1858.034.0568.0797.1056.1341.1434.0544.0377.1161.0634.1812.0754z" fill="#0d90e0"/><path d="m77.3991 76.8667c.1758.041.3605.0187.5215-.063.1609-.0817.2879-.2176.3585-.3837.06-.2334-.3866-.7334-.6666-.7867-.1547-.0061-.3062.0452-.4253.1441s-.1973.2384-.2197.3916c-.0223.1532.0128.3092.0986.438.0859.1288.2164.2213.3664.2597z" fill="#0d90e0"/><path d="m74.5999 69.1536v.12c-.0733.1866-.1867.26-.36.12-.06-.0534-.1-.1334-.1667-.18-.1337-.0909-.2916-.1395-.4533-.1395s-.3196.0486-.4533.1395c-.143.0768-.2531.2032-.3095.3554-.0565.1522-.0555.3197.0028.4712.0482.1932.1452.3707.2818.5154.1366.1448.3082.252.4982.3113-.0549.1805-.0929.3658-.1133.5533 0 .5467.36.76.84.4867.0942-.0398.177-.1025.2408-.1825.0638-.0799.1066-.1746.1245-.2753s.0103-.2043-.0221-.3013-.0885-.1844-.1632-.2542c-.132-.1312-.2723-.2536-.42-.3667.1866-.1133.3066-.2533.4266-.2533.0812.0065.1628-.004.2395-.0311.0768-.027.1471-.0699.2062-.1259.0591-.0559.1057-.1237.1369-.1989s.0462-.1561.0441-.2375c.0274-.0961.0678-.1881.12-.2733.14-.3067.1067-.54-.0933-.6667h-.32c-.0746.0444-.1388.1043-.1882.1756-.0495.0713-.0831.1524-.0985.2378z" fill="#0d90e0"/><path d="m72.2794 72.4402c-.34 0-.7133-.1067-.9866.26-.0651.1-.1527.1834-.2557.2435-.103.0602-.2186.0955-.3377.1032-.1614.0266-.3091.1069-.4192.2279-.11.121-.1761.2756-.1874.4387.0895.2225.223.4245.3926.5941.1695.1695.3716.3031.594.3926.34-.1533.7934-.34 1.24-.5467.0534 0 .08-.1866.0667-.2733-.0186-.1171-.0128-.2367.017-.3514.0298-.1148.0831-.2221.1563-.3153.2-.2933.0267-.76-.28-.7733z" fill="#0d90e0"/><path d="m74.5134 72.86c-.2934-.0666-.5734.2267-.6667.6667-.0009.1289.0403.2547.1173.3581.077.1035.1856.179.3094.2152.1486.0016.2951-.0358.4249-.1083s.2384-.1776.3151-.305c.0128-.1727-.0284-.3451-.118-.4932s-.2232-.2647-.382-.3335z" fill="#0d90e0"/><path d="m77.9997 71.593c-.206-.0599-.4272-.0388-.6181.0592-.1909.0979-.3371.2652-.4086.4675-.0057.2394.0532.476.1705.6848.1174.2088.2887.3822.4962.5018.2127.0283.4283-.0246.6037-.1482.1754-.1235.2979-.3086.343-.5184.0308-.2147-.0099-.4336-.116-.6228-.106-.1892-.2715-.3382-.4707-.4239z" fill="#0d90e0"/><path d="m75.8197 78.5868c-.32-.0667-.5466.1333-.6666.56s.0533.6267.3133.6667c.1756-.0046.3478-.049.5037-.1298.1559-.0809.2914-.1961.3963-.3369.027-.1731-.0152-.3499-.1175-.4921s-.2565-.2385-.4292-.2679z" fill="#0d90e0"/><path d="m78.7058 74.9466c0-.4444.02-.8889.06-1.3333-.1687.1362-.2843.3273-.3267.54-.0287.144-.0193.293.0275.4322s.1293.2637.2392.3611z" fill="#0d90e0"/><path d="m78.8729 78.5339c0-.2134-.0467-.4334-.0667-.6667-.06.0867-.1067.1867-.1467.2466-.04.06.0067.3401.2134.4201z" fill="#0d90e0"/><path d="m68.1792 75.9999c-.0194-.0917-.058-.1783-.1131-.2541-.0551-.0759-.1256-.1392-.2069-.1859-.3734-.1467-.4867.2067-.7.4533.1933.32.3666.5667.7333.4467.0497-.0101.0968-.0303.1385-.0592.0416-.029.0769-.0661.1038-.1092.0268-.043.0446-.0911.0522-.1412.0076-.0502.005-.1013-.0078-.1504z" fill="#0d90e0"/><path d="m76.9727 69.6337c-.0639-.1859-.193-.3423-.3634-.4403-.1703-.0981-.3704-.1311-.5633-.093-.1773.0951-.315.2503-.3883.4378s-.0774.3949-.0116.5851c.0659.1902.1973.3508.3707.4528.1735.1021.3777.1391.5759.1043.3267-.0667.4533-.5867.38-1.0467z" fill="#0d90e0"/><path d="m65.0327 76.26c-.06.22.0933.58.2933.5933.3141.071.6432.0261.9267-.1267.4-.2866.4267-.3266.1933-.84-.24 0-.4333-.06-.6266-.1-.1656-.0237-.3343.0105-.4776.0967s-.2525.2193-.3091.3767z" fill="#0d90e0"/><path d="m79.1602 59.8931c.04-.3-.2934-.6667-.6667-.7267s-.5933.1467-.6667.5933c-.0099.1414.0255.2822.101.4021.0755.1198.1873.2125.319.2646.2667.0333.88-.3.9134-.5333z" fill="#0d90e0"/><path d="m74.4527 57.1998c.1017.032.2092.0416.315.0282s.2074-.0495.298-.1059c.0905-.0563.1678-.1316.2265-.2206.0588-.089.0976-.1896.1138-.2951.0445-.1871.0189-.3841-.0721-.5536-.0909-.1695-.2407-.2998-.4212-.3664-.3533-.1133-.8267.26-.9467.7467-.0237.0825-.0297.1691-.0174.254.0122.085.0423.1664.0883.2388.046.0725.1068.1344.1785.1816s.1526.0787.2373.0923z" fill="#0d90e0"/><path d="m74.9 60.967c.0013-.0497-.0081-.099-.0275-.1447-.0195-.0457-.0485-.0867-.0852-.1202-.0366-.0335-.0801-.0588-.1274-.074-.0472-.0153-.0972-.0202-.1466-.0144-.3733 0-.6266.12-.6666.3066.0123.0807.0404.1582.0828.228.0423.0697.098.1305.1638.1787h.5934c.1079-.094.1827-.2202.2133-.36z" fill="#0d90e0"/><path d="m72.466 69.2198c.0552-.0625.0972-.1354.1235-.2144.0264-.0791.0365-.1626.0299-.2456h-2.58c.0933.3733 0 .52-.42.3933-.1745-.0606-.365-.0562-.5364.0124-.1715.0685-.3125.1967-.397.3609-.1054.1366-.1626.3042-.1626.4767s.0572.3401.1626.4767c.0424.076.1.1424.1694.1949s.149.0899.2336.11c.0847.0201.1726.0223.2582.0065.0855-.0157.1669-.0492.2388-.0981.2292-.1347.4251-.3193.5734-.54.1266-.1733.2333-.3.4533-.2867.32 0 .4267-.1733.4267-.4666.1006.0929.2204.1626.3508.2044.1305.0418.2685.0546.4044.0374.1359-.0171.2664-.0638.3824-.1366.116-.0729.2146-.1702.289-.2852z" fill="#0d90e0"/><path d="m70.5463 71.8337c.0526.1091.1404.1974.2492.2506s.2324.0682.3508.0428c.1117-.0023.219-.0446.3021-.1192.0832-.0747.1369-.1767.1512-.2875.04-.34-.4533-.8733-.6666-.84-.1459.108-.2592.2541-.3275.4223-.0682.1682-.0887.3519-.0592.531z" fill="#0d90e0"/><path d="m69.5935 71.7863c-.1275-.0234-.2592-.0007-.3716.064-.1123.0647-.198.1673-.2417.2894-.0339.1459-.0204.2988.0383.4366s.1597.2534.2883.33c.1755.0442.3608.0265.5247-.05s.2965-.2072.3753-.37c.06-.2666-.2066-.56-.6133-.7z" fill="#0d90e0"/><path d="m72.2127 58.5135c0-.2534-.12-.3734-.34-.42-.0587-.0158-.1201-.0195-.1802-.0108-.0602.0088-.118.0297-.1698.0615-.0518.0319-.0965.0739-.1315.1237-.035.0497-.0594.1061-.0719.1656-.0094.0542-.0074.1098.006.1632.0134.0535.0378.1035.0718.1468.0339.0434.0766.0791.1252.105.0486.0258.1021.0411.157.045.1208.0176.2436-.0121.343-.0829s.1676-.1773.1904-.2971z" fill="#0d90e0"/><path d="m67.7533 76.9133c-.32-.0867-.54.2267-.6.4067s.2866.5333.6666.4333c.0689-.0283.1304-.0717.18-.1272.0497-.0554.0862-.1213.1067-.1928.0125-.0575.0133-.1169.0021-.1746-.0112-.0578-.034-.1127-.067-.1613-.0331-.0487-.0757-.0901-.1253-.1217-.0496-.0317-.1051-.0529-.1631-.0624z" fill="#0d90e0"/><path d="m67.3334 74.3735c-.4467-1.1467-1.12-1.2467-1.88-.3333-.0796-.0184-.1576-.0429-.2333-.0734-.115-.0615-.2497-.0751-.3746-.0376-.125.0375-.23.1229-.2921.2376-.0729.1034-.112.2269-.112.3534s.0391.2499.112.3533c.3133.6667.6133.7667 1.1867.4667.4333-.22.88-.4267 1.3333-.6667.5333.84.6667.8667 1.1867.32-.0135-.1048-.0493-.2054-.1049-.2952s-.1298-.1666-.2176-.2254c-.0878-.0587-.1871-.098-.2913-.1151-.1042-.0172-.2109-.0118-.3129.0157z" fill="#0d90e0"/><path d="m71.2198 60.7134c-.0194.0515-.0275.1066-.0235.1615.0039.055.0198.1084.0464.1565.0266.0482.0634.0901.1078.1226.0444.0326.0954.0551.1493.0661.115.0382.2403.0295.3489-.0242.1086-.0536.1917-.148.2311-.2625.0116-.0583.0114-.1183-.0005-.1765s-.0354-.1134-.0689-.1624c-.0335-.0491-.0765-.0909-.1264-.1232-.0499-.0322-.1057-.0542-.1642-.0646-.053-.0138-.1082-.0168-.1624-.0087-.0542.008-.1062.0269-.1529.0556-.0467.0286-.0871.0664-.1189.111-.0317.0447-.0541.0953-.0658.1488z" fill="#0d90e0"/><path d="m80.1133 61.3335h.76c-.0404-.0731-.1072-.128-.1867-.1534-.0662-.0206-.1363-.0253-.2046-.0138-.0684.0116-.133.0392-.1887.0805-.0533.0267-.12.0467-.18.0867z" fill="#0d90e0"/><path d="m67.526 85.4937c-.1686-.0366-.3447-.0142-.4989.0635-.1541.0776-.2768.2058-.3477.3632-.0339.1281-.0175.2643.0457.3808.0631.1164.1684.2044.2943.2459.1573.0273.3192-.0028.4562-.0848.137-.0819.2401-.2104.2904-.3619.0252-.0555.039-.1154.0405-.1763.0015-.0608-.0093-.1214-.0317-.178s-.0559-.1082-.0987-.1515c-.0427-.0434-.0938-.0777-.1501-.1009z" fill="#0d90e0"/><path d="m71.2595 82.9801c.18.0159.3603-.0277.5132-.124s.2701-.2401.3335-.4093c.0667-.26-.2133-.6067-.5467-.6667-.4333-.0933-.6666 0-.7666.3467-.0304.1741-.0008.3533.084.5083s.2197.2767.3826.345z" fill="#0d90e0"/><path d="m71.4735 84.1397c0-.1467-.0533-.5267-.2267-.5533-.1733-.0267-.3533.3333-.38.4666-.0266.1334.16.26.28.3067s.3467-.0133.3267-.22z" fill="#0d90e0"/><path d="m72.5133 86.3537c-.1667 0-.5867.0733-.6134.24-.0266.1667.3134.3933.46.4733.1467.08.3934-.1133.42-.3266.0267-.2134-.0533-.3934-.2666-.3867z" fill="#0d90e0"/><path d="m71.5331 90.8738c-.0842.033-.1525.097-.191.1789-.0384.0818-.044.1753-.0156.2611.0866.3401.3533.3801.6666.3934.1467-.2667.3601-.48.1334-.74-.0728-.0818-.1718-.1358-.28-.1529-.1082-.017-.2189.0041-.3134.0595z" fill="#0d90e0"/><path d="m69.3929 83.3332c-.2373-.0378-.4804.0022-.693.1139-.2127.1117-.3835.2893-.487.5061-.0343.209.0073.4234.1174.6044.11.181.2812.3166.4826.3823.2051.0478.4207.0187.6058-.0819s.3268-.2655.3983-.4637c.0715-.1981.0679-.4156-.0103-.6112-.0782-.1955-.2254-.3556-.4138-.4499z" fill="#0d90e0"/><path d="m70.6125 78.1469c-.0704-.0614-.1207-.1425-.1445-.2328-.0237-.0904-.0198-.1858.0112-.2739.002-.1405-.0503-.2764-.1459-.3794-.0956-.1031-.2272-.1653-.3675-.1739-.2933 0-.3733.1534-.3933.3934.0056.1742-.0286.3475-.1.5066-.2.32-.4533.6067-.6667.9267-.9066-.2533-1.18-.0933-1.1933.74-.0134.1043-.0001.2103.0387.3081.0388.0977.1018.184.1832.2507.0813.0666.1783.1115.2818.1304.1034.0189.21.0111.3096-.0225.351-.148.686-.3312 1-.5467.1706.1356.3776.2176.5949.2354.2172.0178.4348-.0294.6252-.1354.121-.11.2172-.2446.2819-.3948.0648-.1502.0967-.3126.0935-.4761-.0031-.1636-.0413-.3245-.1118-.4721s-.1717-.2785-.297-.3837z" fill="#0d90e0"/><path d="m67.5597 82.8198c-.2248.0333-.4475.0801-.6667.14-.4467-.3467-.8867-.4067-1.0933-.1467-.0962.1764-.1319.3794-.1018.578.0301.1987.1244.382.2684.522.3.2467.5067.1733 1.02-.4133.2467.18.48.3933.7534.0933.0808-.0769.1323-.1795.1457-.2902.0133-.1107-.0122-.2226-.0724-.3165-.0667-.08-.18-.1733-.2533-.1666z" fill="#0d90e0"/><path d="m69.1664 87.1465c-.1832-.0094-.3636.0485-.5072.1626-.1436.1142-.2406.2768-.2728.4574-.0734.3867.16.6667.6066.74.0876.0248.1794.0302.2693.0158.0898-.0143.1754-.048.2509-.0987.0755-.0508.139-.1174.1862-.1951.0472-.0778.0768-.1649.087-.2553.0266-.1918-.0236-.3863-.1397-.5411-.1162-.1549-.2888-.2575-.4803-.2856z" fill="#0d90e0"/><path d="m72.9999 89.1399c-.0866 0-.28-.0534-.34.0866s.1134.26.16.26c.0503-.0006.1-.0116.1459-.0323.0458-.0206.087-.0505.1208-.0877 0 0-.04-.2133-.0867-.2266z" fill="#0d90e0"/><path d="m69.1395 81.3335c.0075-.0749-.001-.1505-.0252-.2218-.0241-.0713-.0633-.1366-.1148-.1915-.0505-.0362-.1089-.0598-.1703-.0691s-.1241-.0039-.183.0158c-.26.1466-.2534.38-.12.6666.2333.0134.5.0867.6133-.2z" fill="#0d90e0"/><path d="m73.0065 80.0737c.0242-.1471-.0086-.2978-.0915-.4217-.083-.1238-.21-.2114-.3552-.245-.098-.0245-.2018-.0096-.289.0415-.0873.0511-.151.1343-.1776.2319-.0475.1017-.0589.2167-.0322.3257.0267.1091.0897.2059.1788.2743.0634.0388.134.0643.2076.0749.0735.0105.1485.006.2202-.0133.0718-.0193.1389-.0531.1971-.0992.0583-.0462.1065-.1037.1418-.1691z" fill="#0d90e0"/><path d="m78.7664 84.2871c.0038-.1542-.0459-.3048-.1408-.4264-.0948-.1215-.2288-.2064-.3792-.2403-.32-.0733-.5333.04-.6.32-.0532.1834-.0379.3799.043.5528.081.173.2221.3106.397.3872.1648-.0041.3229-.0656.4471-.174.1242-.1083.2066-.2567.2329-.4193z" fill="#0d90e0"/><path d="m78.3332 80.6668c.1-.4134-.2133-.7934-.7467-.9134-.0907-.029-.1865-.0383-.2811-.0272s-.1856.0424-.2671.0917c-.0815.0492-.1515.1154-.2053.194-.0537.0785-.0901.1677-.1065.2615-.0425.2257.0028.4591.1268.6525.1239.1933.3171.332.5399.3875.2066.0137.4118-.0426.5823-.16.1706-.1173.2966-.2888.3577-.4866z" fill="#0d90e0"/><path d="m73.6661 81.833c-.0933.0933-.2933.2067-.3733.3733-.08.1667.1266.5134.4066.5534.055.0074.111.0035.1644-.0115s.1032-.0408.1463-.0758.0786-.0784.1043-.1276c.0256-.0492.041-.1031.045-.1585.0023-.1373-.0471-.2705-.1385-.373-.0914-.1026-.2181-.1669-.3548-.1803z" fill="#0d90e0"/><path d="m73.0535 54.2596c.2131.0163.4254-.0409.6013-.1623.176-.1213.305-.2994.3654-.5044.015-.1768-.0408-.3523-.1552-.488-.1144-.1356-.278-.2203-.4548-.2353s-.3524.0408-.488.1552c-.1357.1144-.2203.278-.2353.4548-.0503.1522-.0403.3179.0279.463s.1894.2586.3387.317z" fill="#0d90e0"/><path d="m76.8199 88.7002c-.068-.0724-.1501-.1302-.2412-.1697-.0912-.0394-.1895-.0598-.2888-.0598-.0994 0-.1977.0204-.2888.0598-.0912.0395-.1733.0973-.2412.1697-.0759.1198-.1117.2606-.1021.4021s.064.2762.1554.3846c.075.112.19.1911.3215.2208.1314.0298.2693.008.3852-.0608.2066-.1.6666-.1867.7066-.5267s-.2866-.3066-.4066-.42z" fill="#0d90e0"/><path d="m77.7792 68.7598c-.0173.1024-.001.2076.0466.3.1375.2623.3554.4737.6217.6033.2663.1295.5671.1704.8583.1167.0756-.3423.1556-.6823.24-1.02z" fill="#0d90e0"/><path d="m74.7861 81.6662c.1165.0263.2388.0063.3408-.056.1021-.0622.1759-.1617.2058-.2774.0239-.1237-.0024-.2519-.073-.3563s-.1799-.1765-.3036-.2003c-.0613-.0118-.1243-.0115-.1855.0011-.0611.0125-.1192.0369-.1709.0719-.1044.0707-.1764.1799-.2003.3036-.0001.116.0376.2289.1073.3215.0698.0926.1679.16.2794.1919z" fill="#0d90e0"/><path d="m75.9998 83.4203c.0096-.1126-.0246-.2246-.0953-.3127-.0708-.0881-.1727-.1456-.2847-.1606-.3667-.0467-.6667.3733-.6667.52.0464.1379.1367.257.2571.3388.1203.0819.2642.1221.4096.1145.1164-.0166.2216-.0783.2927-.172.0711-.0936.1025-.2114.0873-.328z" fill="#0d90e0"/><path d="m74.4267 77.2334-.04-.06c.0836.0923.1829.169.2933.2267.0393.0246.084.0393.1303.0429.0463.0035.0927-.0042.1353-.0226.0427-.0183.0802-.0467.1094-.0828s.0492-.0787.0583-.1242c.0224-.0458.0345-.096.0352-.147.0008-.051-.0097-.1016-.0307-.1481s-.052-.0877-.0908-.1208c-.0388-.0332-.0845-.0573-.1337-.0708-.1518-.0122-.3041.0178-.44.0867.0184-.1031-.0031-.2092-.06-.2971s-.1451-.1508-.2466-.1762c-.0736-.0154-.1498-.013-.2223.0069-.0724.0199-.1391.0568-.1945.1077-.0554.0508-.0979.1141-.1239.1846-.0261.0705-.035.1461-.026.2208 0 .32-.18.4133-.3733.5933-.4934-.5067-.92-.4733-1.2267.06-.032.0505-.0588.1041-.08.16-.1533.3933-.0467.62.3533.7333.4.1134.6667.1534.9534-.3266.0557.1571.0915.3206.1066.4866-.0219.1104-.0001.2249.0608.3195.061.0945.1563.1617.2659.1872.1195.0536.2522.0708.3814.0495.1292-.0214.2493-.0803.3453-.1695.64-.7133.6533-.9933.06-1.72z" fill="#0d90e0"/><path d="m74.9791 85.9998c-.1094-.0152-.2208.0067-.3163.0623s-.1696.1417-.2103.2444c-.0178.1194.003.2413.0594.348.0563.1068.1453.1927.2539.2453.1094.0227.2232.0029.3186-.0552.0953-.0582.1649-.1505.1947-.2581.0282-.1175.0134-.2412-.0416-.3488-.055-.1075-.1466-.1919-.2584-.2379z" fill="#0d90e0"/><path d="m78.8194 87.0601c-.0521-.0174-.1071-.0241-.1619-.0197-.0547.0044-.1079.0198-.1565.0454-.0486.0255-.0915.0606-.1262.1032-.0346.0425-.0603.0917-.0755.1444-.0223.1093-.0041.2229.051.3198.0552.0969.1437.1704.249.2069.0515.0207.1067.0301.1621.0276.0553-.0025.1095-.0169.1588-.0422s.0926-.0609.127-.1044.059-.0938.0722-.1476c.0198-.11.0007-.2234-.054-.3207-.0548-.0974-.1418-.1726-.246-.2127z" fill="#0d90e0"/><path d="m69.8128 55.2067c.1204.0138.2421-.012.3467-.0733s.1864-.1549.2333-.2667c.1066-.44-.0467-.8467-.3467-.9067-.171-.0024-.3384.0491-.4784.1473-.14.0981-.2455.2379-.3016.3994-.06.24.26.6533.5467.7z" fill="#0d90e0"/><path d="m66.6134 93.0799c-.0057-.0601-.0315-.1165-.0734-.16z" fill="#0d90e0"/><path d="m65.9993 60.3004c.1466 0 .4133 0 .4666-.1734.0534-.1733-.1266-.38-.2533-.4933s-.2867 0-.3867.12l-.0666.18c-.0133.04-.0173.0824-.0118.1242s.0203.0818.0433.117c.0231.0352.0538.0648.0899.0865.0361.0218.0766.0351.1186.039z" fill="#0d90e0"/><path d="m67.1664 61.333h.4133c.0931-.0709.1966-.1271.3067-.1667.1399-.0288.2645-.1077.3505-.2218.0859-.1142.1272-.2557.1161-.3982 0-.3333-.2266-.6667-.18-.7933.0467-.1267.1534-.3334.3934-.24.24.0933.6266-.0467.7933-.5467s-.2733-1.04-.6667-.9267c-.3933.1134-.4.3534-.5333.2934s-.3133-.3267-.5533-.2267-.2534.8733-.1934.9867c.06.1133.3467.3466.1534.5333-.1934.1867-.5267.2333-.5867.5067-.06.2733-.2266.9133.1267 1.1867z" fill="#0d90e0"/><path d="m68.1727 57.0195c.46.16.76-.2533.9066-.4733.1467-.22-.2666-.6667-.6666-.6667-.1115-.012-.2235.0183-.3137.085-.0901.0667-.1519.1649-.173.275-.1067.2867.0133.7.2467.78z" fill="#0d90e0"/><path d="m71.4802 55.6471c-.0562.2366-.0242.4856.0899.7003.1141.2148.3026.3806.5301.4664.2317.0296.4666-.0202.6663-.1413.1998-.1211.3527-.3063.4337-.5254.1266-.4266-.2-.8666-.78-1.0266-.0963-.0284-.1973-.0372-.2971-.0259-.0997.0112-.1962.0423-.2838.0914-.0875.049-.1644.1151-.2261.1943s-.1069.1699-.133.2668z" fill="#0d90e0"/><path d="m70.2797 56.5466c-.119-.0227-.2421.0004-.3447.0647s-.1772.165-.2087.2819c-.0143.1107.0134.2227.0776.314.0641.0913.1601.1553.2691.1794.0529.0194.1092.0272.1654.0229.0561-.0043.1106-.0206.1599-.0477.0493-.0272.0922-.0647.1258-.1098.0336-.0452.0571-.0971.0689-.1521.0192-.0569.0262-.1171.0205-.1769-.0057-.0597-.0239-.1176-.0535-.1698-.0295-.0522-.0698-.0976-.1181-.1332s-.1036-.0606-.1622-.0734z" fill="#0d90e0"/><path d="m71.153 53.167c.2067.0734.3133-.12.3467-.3066.0333-.1867-.1-.4467-.2934-.42-.1933.0266-.4866 0-.4933.2866-.0067.2867.2933.3934.44.44z" fill="#0d90e0"/><path d="m69.4132 59.913c-.0026.2195.0672.4338.1986.6097.1313.1759.3168.3037.528.3637.2667.0466.5533-.2267.6133-.5934.06-.3666-.0933-.6666-.5533-.7533-.36-.0667-.7333.1067-.7866.3733z" fill="#0d90e0"/><path d="m70.0995 52.6663c.0727-.0122.1422-.0392.2041-.0794.0619-.0401.115-.0925.1559-.1539.0866-.1733-.1467-.4667-.3534-.4467-.114.0303-.2192.0875-.3066.1667-.0467.28.1066.5133.3.5133z" fill="#0d90e0"/><path d="m66.413 80.3798c.133-.0482.2622-.1061.3867-.1734.4133-.2.5533-.46.44-.8066-.0288-.0903-.0751-.174-.1363-.2463-.0611-.0723-.136-.1319-.2202-.1752-.0842-.0434-.1762-.0697-.2706-.0774-.0944-.0078-.1894.0032-.2796.0322-.4359.126-.8821.213-1.3333.26-.3667 0-.78.3067-.7534.56.0383.1099.1107.2048.2066.2707.0959.066.2104.0996.3268.096.32-.1067.4133.12.6066.2333.1941.0891.4067.1302.62.12.1408-.0014.2795-.0332.4067-.0933z" fill="#0d90e0"/><path d="m64.2527 85.5404c-.0784-.0226-.1616-.0226-.24 0l.22.8733c.0897-.0122.1732-.0523.239-.1144.0658-.0622.1104-.1434.1277-.2322.009-.1139-.0202-.2275-.083-.3229s-.1556-.1671-.2637-.2038z" fill="#0d90e0"/><path d="m64.5927 82.4329c-.0253-.1546-.0868-.301-.1795-.4273s-.2139-.2289-.3538-.2994c-.2533-.0666-.44.0934-.5133.4467-.0734.3533 0 .6133.2866.6667.3867.0933.7067-.0734.76-.3867z" fill="#0d90e0"/><path d="m67.0595 80.4529c-.1286-.0341-.2651-.0228-.3865.0318s-.2203.1493-.2801.2682c-.1334.4467 0 .86.28.94.1706.0227.3439-.0122.4924-.0992.1485-.0869.2638-.2209.3276-.3808.0315-.1576.005-.3213-.0746-.461-.0796-.1396-.207-.2458-.3588-.299z" fill="#0d90e0"/><path d="m65.5734 86.9202c-.034-.0022-.0677.0081-.0947.0289-.0271.0209-.0455.0509-.0519.0845 0 .04.0733.1266.1066.1266.0289.0038.0582-.002.0835-.0164.0253-.0145.0451-.0368.0565-.0636.004-.0171.0045-.0347.0016-.052s-.0091-.0339-.0184-.0487c-.0093-.0149-.0214-.0278-.0357-.0379-.0143-.0102-.0304-.0174-.0475-.0214z" fill="#0d90e0"/><path d="m63.4666 75.82c.0578-.1456.0683-.3058.03-.4577-.0383-.152-.1234-.288-.2433-.3889-.0728-.0301-.1521-.041-.2303-.0317-.0782.0094-.1527.0386-.2164.085v.4333.86h.0467c.1891.0311.3831-.0067.5467-.1066.0586-.0451.0981-.1107.1105-.1836.0123-.0729-.0033-.1479-.0439-.2098z" fill="#0d90e0"/><path d="m63.3327 72.2663c.0993-.1541.1366-.3401.1044-.5206-.0321-.1806-.1313-.3422-.2777-.4527.0858-.0603.1583-.1374.2133-.2267.0494-.0826.0754-.177.0754-.2733 0-.0962-.026-.1907-.0754-.2733.0688.0059.138.0059.2067 0 .2453-.0561.4604-.2025.6025-.41.142-.2076.2007-.4611.1642-.71.0651.0204.1349.0204.2 0 .0474-.0272.0886-.0639.121-.1079s.0553-.0942.0672-.1475c.0119-.0534.0126-.1086.002-.1621-.0106-.0536-.0322-.1044-.0636-.1492h-.54c-.0696.0818-.1075.1859-.1066.2933-.0974-.0869-.2182-.1434-.3473-.1624-.1292-.0189-.2611.0004-.3794.0558-.1933 1.2466-.32 2.52-.4 3.8066.14-.1.2933-.2333.4333-.56z" fill="#0d90e0"/><path d="m33.5663 123.433c-.2927 0-.58-.079-.832-.228-.2521-.149-.4597-.362-.6013-.618-.629-1.159-1.1877-2.355-1.6733-3.58-.0784-.2-.1167-.412-.1129-.626s.0498-.425.1352-.622c.0854-.196.2087-.373.3627-.522s.3358-.265.535-.344c.1991-.078.4118-.117.6258-.113s.4251.05.6214.135c.1962.086.3737.209.5223.363s.2655.336.3438.535c.4318 1.102.9328 2.175 1.5 3.214.1374.247.2077.526.2037.809s-.082.56-.2263.803c-.1443.244-.3499.445-.5962.584-.2463.14-.5249.212-.8079.21zm-3.44-10.826c-.4007-.001-.7869-.15-1.0846-.419-.2977-.268-.486-.636-.5287-1.035-.1201-1.073-.1802-2.153-.18-3.233 0-.24 0-.487 0-.733.0122-.431.1943-.84.5065-1.137s.7292-.459 1.1602-.45c.2136.006.424.054.619.141.1951.088.371.213.5177.368.1468.155.2614.338.3375.538.076.2.112.413.1058.626v.667c0 .962.0534 1.924.16 2.88.0467.429-.0787.858-.3487 1.195-.2701.336-.6626.551-1.0913.598zm1.1267-11.274c-.172 0-.3429-.027-.5067-.08-.2031-.066-.3912-.172-.5535-.311-.1622-.139-.2955-.309-.392-.5-.0966-.191-.1547-.399-.1709-.6117-.0162-.2131.0098-.4274.0764-.6305.4094-1.2638.9107-2.496 1.5-3.6866.1919-.3872.5297-.6824.9391-.8205.4095-.1382.8571-.108 1.2443.0838s.6823.5297.8205.9391c.1381.4095.108.857-.0839 1.2442-.5253 1.0472-.971 2.1325-1.3333 3.2462-.1062.326-.312.61-.5884.812-.2763.202-.6092.312-.9516.315zm5.8-9.7332c-.3189 0-.6308-.0937-.8969-.2695-.266-.1759-.4745-.426-.5996-.7194-.125-.2934-.161-.6171-.1036-.9308.0575-.3137.2059-.6036.4268-.8336.9152-.9554 1.8953-1.8464 2.9333-2.6667.1641-.1567.3591-.2773.5725-.3541.2135-.0769.4406-.1082.6669-.0921.2263.0162.4467.0795.6471.1859.2003.1064.3762.2535.5163.432.1402.1784.2414.3841.2973.604s.0651.449.0272.6727c-.0379.2236-.1222.4369-.2475.626-.1252.1892-.2887.3501-.4798.4723-.9195.7312-1.7882 1.5242-2.6 2.3733-.1512.1538-.3319.2756-.5312.358-.1993.0825-.4131.124-.6288.122zm9.3333-6.4333c-.3761.0051-.7424-.1203-1.0364-.3549-.2941-.2346-.4977-.5639-.5763-.9317-.0786-.3679-.0273-.7516.1452-1.0859s.4555-.5984.8009-.7475c1.203-.513 2.4338-.9581 3.6866-1.3333.2045-.0631.4193-.0852.6323-.0652s.4199.0817.6091.1817c.1891.1.3567.2362.4931.401.1365.1647.2392.3547.3022.5591.063.2045.0852.4193.0652.6323s-.0817.4199-.1817.6091c-.1.1891-.2362.3567-.401.4931-.1647.1365-.3547.2392-.5592.3022-1.1339.3465-2.2469.7582-3.3333 1.2333-.2015.0685-.4142.0979-.6267.0867zm33.88-2.6667c-.4314.0045-.8469-.1627-1.1551-.4646-.3082-.302-.4838-.7139-.4882-1.1454-.0044-.4314.1627-.8469.4647-1.1551.3019-.3082.7139-.4838 1.1453-.4882 1.2333 0 2.48-.0667 3.7067-.12.4314-.0203.8532.1315 1.1727.4222.3194.2907.5103.6964.5306 1.1278s-.1315.8532-.4222 1.1727c-.2907.3194-.6964.5103-1.1278.5306-1.26.0534-2.5333.1-3.7933.12zm-7.68 0h-.0466c-1.24-.0311-2.5-.0777-3.78-.14-.2136-.01-.4232-.0621-.6167-.1531-.1935-.0911-.3672-.2193-.5111-.3775s-.2553-.3432-.3277-.5444c-.0725-.2012-.1046-.4147-.0945-.6283.01-.2136.0621-.4232.1531-.6167.0911-.1935.2194-.3671.3775-.5111.1582-.1439.3432-.2553.5444-.3277.2012-.0725.4147-.1046.6283-.0945 1.2533.06 2.4933.1067 3.7133.1333.4143.0312.8012.219 1.0819.5253s.4341.7081.429 1.1235c-.0051.4155-.1683.8133-.4564 1.1127-.2882.2993-.6795.4776-1.0945.4985zm-15.1733-.1066c-.4214.0075-.8292-.1488-1.1376-.436-.3085-.2872-.4934-.6829-.5159-1.1037-.0224-.4209.1193-.834.3954-1.1524s.665-.5172 1.0848-.5546c1.2533-.14 2.5533-.2267 3.8666-.2667.2151-.0107.4302.022.6323.0963s.3871.1887.544.3362.2823.3252.3689.5224.1324.4098.1348.6251c.0063.2137-.0297.4264-.1057.6262-.0761.1997-.1908.3825-.3375.5379s-.3226.2804-.5177.3678c-.1951.0873-.4054.1354-.6191.1415-1.2266.04-2.4466.12-3.6133.2466zm34.26-.5534c-.4199.0035-.8248-.1555-1.1302-.4437-.3053-.2883-.4873-.6834-.508-1.1028s.1217-.8305.3972-1.1474.6628-.5149 1.081-.5527c1.2333-.1267 2.4733-.2734 3.6733-.4334.2119-.028.4272-.014.6337.0412.2064.0552.4.1505.5696.2805s.312.2922.419.4772c.1069.185.1764.3893.2044.6011.028.2119.014.4272-.0412.6337-.0552.2064-.1505.4-.2805.5696s-.2921.312-.4771.419c-.1851.1069-.3893.1764-.6012.2044-1.2333.1667-2.5.3133-3.7733.4467zm11.333-1.7066c-.402-.0003-.791-.15-1.09-.4201-.298-.2702-.486-.6416-.527-1.0424s.069-.8024.307-1.1271c.239-.3247.589-.5493.984-.6304 1.213-.2534 2.426-.52 3.6-.8134.42-.1025.863-.034 1.233.1904.369.2244.634.5864.737 1.0063.102.4199.034.8634-.191 1.2328-.224.3695-.586.6346-1.006.7372-1.213.2933-2.467.5733-3.72.8333-.114.0018-.228-.0071-.34-.0266zm10.994-3.0267c-.386.0007-.759-.1357-1.054-.3848-.294-.249-.49-.5946-.554-.975-.063-.3804.011-.7709.209-1.1019.198-.3309.507-.5808.872-.705 1.167-.4 2.327-.8266 3.44-1.2733.401-.16.85-.154 1.247.0166.397.1707.71.492.87.8934s.154.8498-.017 1.2468-.492.7099-.893.8699c-1.167.46-2.38.9066-3.594 1.3333-.17.0557-.348.0827-.526.08zm10.473-4.5467c-.36-.0033-.709-.1257-.993-.3482-.284-.2224-.486-.5324-.575-.8816s-.06-.7181.083-1.0491c.142-.331.39-.6056.705-.7811 1.087-.5866 2.14-1.2066 3.14-1.84.18-.1147.382-.1927.592-.2296.211-.037.426-.0321.635.0143.209.0465.406.1335.581.2563.175.1227.324.2787.439.459.114.1804.192.3816.229.5921s.032.4262-.014.6348-.134.4061-.256.5811c-.123.1749-.279.324-.459.4387-1.06.6667-2.18 1.3333-3.334 1.9533-.228.128-.485.1968-.746.2zm9.333-6.52c-.328-.0008-.648-.101-.919-.2874-.27-.1863-.478-.4502-.595-.7567-.118-.3066-.14-.6416-.063-.9609.076-.3194.247-.6081.491-.8283.9-.82 1.76-1.6667 2.553-2.5267.292-.3182.698-.5076 1.129-.5263.432-.0188.853.1346 1.171.4263.318.2918.508.698.526 1.1293.019.4314-.134.8525-.426 1.1707-.86.9334-1.793 1.86-2.78 2.7467-.285.2555-.651.4019-1.033.4133zm7.167-8.8333c-.283.0005-.562-.0729-.808-.213-.246-.1402-.451-.3421-.595-.5859-.144-.2437-.222-.5209-.226-.8041-.004-.2831.065-.5625.202-.8103.347-.6667.667-1.28.967-1.92.193-.44.393-.88.593-1.3334.088-.1956.213-.3721.369-.5194s.339-.2624.539-.3389c.2-.0764.414-.1127.628-.1067s.425.0542.621.1417.372.2128.519.3685c.148.1558.263.3391.339.5393.077.2003.113.4136.107.6279s-.054.4253-.142.621l-.606 1.3333c-.334.7267-.667 1.4467-1.087 2.1533-.134.2483-.331.4572-.57.6057-.24.1486-.515.2317-.797.241zm-9.133-8.0867c-.208-.0011-.414-.0419-.607-.12-1.199-.4887-2.37-1.0452-3.507-1.6666-.195-.0972-.369-.2328-.511-.3986-.142-.1659-.249-.3586-.315-.5667s-.089-.4274-.069-.6447c.021-.2173.085-.4283.189-.6203s.245-.3612.416-.4974c.17-.1362.367-.2367.577-.2954.21-.0588.43-.0746.647-.0466.216.028.425.0993.613.2097 1.048.57 2.127 1.0797 3.233 1.5266.354.1394.648.3983.831.7318.182.3334.242.7203.17 1.0935-.073.3732-.275.709-.569.9492-.295.2402-.665.3695-1.045.3655zm13.586-2.4533c-.172-.0005-.343-.0275-.506-.08-.204-.0671-.392-.1736-.554-.3136-.162-.1399-.295-.3104-.391-.5018s-.153-.3999-.168-.6135c-.016-.2135.012-.428.079-.6311.394-1.2067.747-2.38 1.027-3.4933.107-.4182.376-.7767.747-.9968.371-.22.815-.2835 1.233-.1766.418.107.777.3757.997.747.22.3714.283.8149.176 1.233-.3 1.1867-.666 2.4334-1.093 3.7134-.108.3246-.316.607-.593.8068-.278.1999-.612.3072-.954.3065zm-23.086-3.6933c-.218.0007-.434-.0426-.634-.1274-.201-.0848-.382-.2093-.533-.366-.954-.975-1.803-2.0475-2.533-3.2-.231-.3651-.307-.8069-.212-1.2282.095-.4214.353-.7877.718-1.0184.365-.2308.807-.307 1.229-.212.421.095.787.3535 1.018.7186.608.9614 1.316 1.8553 2.113 2.6667.223.2293.373.5192.432.8334s.024.6388-.1.9333c-.125.2944-.333.5457-.6.7224-.266.1766-.579.2709-.898.2709zm25.333-7.4867h-.04c-.214-.0052-.424-.0524-.62-.139-.195-.0866-.371-.2108-.519-.3656-.147-.1548-.262-.3371-.339-.5365-.077-.1993-.114-.4119-.109-.6256 0-.2133 0-.42 0-.6266.004-.9481-.063-1.8952-.2-2.8334-.031-.2114-.021-.427.031-.6344.052-.2073.144-.4025.271-.5743s.287-.3168.47-.4269c.184-.11.387-.1828.598-.2144.212-.0315.427-.021.635.0307.207.0518.402.144.574.2712s.317.287.427.4703.183.3864.214.5978c.165 1.1035.245 2.2178.24 3.3334v.7133c-.017.4256-.201.8274-.511 1.1191s-.723.45-1.149.4409zm-30-2.6667c-.416.0119-.82-.1353-1.131-.4116s-.505-.6608-.542-1.075c0-.42-.04-.8534-.04-1.28v-.1067c.006-.9106.077-1.8196.213-2.72.033-.2114.108-.4142.22-.5968.111-.1826.258-.3415.431-.4674.173-.126.369-.2167.577-.2669.208-.0501.424-.0588.635-.0256.212.0333.415.1079.597.2195.183.1117.342.2582.468.4312.126.1731.216.3692.266.5773.051.208.059.4239.026.6354-.115.739-.175 1.4855-.18 2.2333v.0333c0 .36 0 .7067.04 1.0534.029.4312-.113.8565-.397 1.1827-.284.3261-.685.5265-1.116.5573zm26.893-8.0933c-.248.0011-.493-.0549-.716-.1635-.223-.1087-.418-.2671-.57-.4632-.688-.8757-1.483-1.6619-2.367-2.34-.169-.1313-.311-.2947-.418-.4808-.106-.1862-.174-.3915-.201-.6041-.055-.4295.064-.8629.329-1.2051.265-.3421.655-.5649 1.085-.6192.429-.0544.863.064 1.205.3292 1.103.8456 2.095 1.8266 2.953 2.92.187.2401.303.5279.335.8307.032.3027-.022.6083-.155.8819-.134.2737-.341.5044-.599.666s-.556.2475-.861.2481zm-23.533-2.5c-.316.0003-.625-.0914-.89-.2639s-.473-.4183-.6-.7075c-.128-.2892-.168-.6092-.116-.9209.051-.3117.192-.6016.406-.8344.965-1.058 2.089-1.9578 3.333-2.6666.375-.214.82-.2702 1.236-.1565.416.1138.77.3883.984.7631.214.3749.271.8193.157 1.2357-.114.4163-.388.7704-.763.9843-.941.5319-1.793 1.2066-2.527 2-.152.1796-.343.3235-.557.4214s-.447.1475-.683.1453zm14-3.4667c-.142-.0002-.283-.0204-.42-.06-1.089-.2946-2.207-.4668-3.333-.5133-.214-.0086-.424-.0592-.618-.149s-.369-.2169-.513-.3742c-.145-.1573-.258-.3416-.331-.5424-.074-.2007-.107-.4141-.098-.6277.024-.4292.214-.832.53-1.1232s.733-.4479 1.163-.4368c1.37.0567 2.73.2669 4.053.6266.383.1006.716.3374.937.666s.315.7265.263 1.1191c-.051.3925-.244.7529-.543 1.0133-.298.2605-.681.4033-1.077.4016z" fill="#294492"/><path d="m139.447 49.7266c-.62.4333-8.36 5.0333-15.687 6.2466 3.363 3.1587 5.851 7.1336 7.223 11.5384s1.581 9.0897.606 13.5991c-.974 4.5095-3.098 8.6901-6.167 12.1355-3.068 3.4455-6.975 6.0385-11.342 7.5268.42 5.374.98 10.74 1.58 16.1 6.853-1.696 13.179-5.066 18.409-9.808s9.202-10.7073 11.56-17.362c2.357-6.6547 3.026-13.7904 1.947-20.7674s-3.872-13.5772-8.129-19.209z" fill="#1ba9f5"/><path d="m133.333 107.814c.021-.047.037-.097.047-.147-.207.18-.4.36-.607.527.121-.001.239-.037.338-.105.1-.068.177-.164.222-.275z" fill="#0066b1"/><path d="m137.907 105.913c.183.031.371.015.545-.047.175-.062.332-.167.455-.306.021-.128.004-.259-.048-.377s-.137-.219-.246-.29c0-.226-.18-.366-.52-.44-.143-.028-.293 0-.416.078-.124.078-.214.2-.25.342.004.186.062.367.166.52.008.106.04.208.094.298.055.09.13.167.22.222z" fill="#0066b1"/><path d="m139.187 104.52c.313.073.642.03.926-.12l.047-.04c.077.006.155-.012.222-.05.068-.037.123-.095.158-.164.039-.17.032-.349-.022-.515-.054-.167-.153-.316-.285-.431-.06-.03-.126-.046-.193-.049s-.134.008-.197.033c-.063.024-.12.061-.167.109-.048.047-.085.104-.109.167-.146.003-.288.049-.407.134-.118.084-.209.202-.26.339-.073.213.087.573.287.587z" fill="#0066b1"/><path d="m138.666 103.333c0-.053-.04-.167-.093-.187-.157-.089-.281-.227-.353-.393-.154.18-.3.367-.46.547.17.092.346.175.526.246.154.1.274.014.38-.213z" fill="#0066b1"/><path d="m137.373 103.713c-.067.08-.134.153-.207.227.037-.01.073-.023.107-.04.029-.023.052-.051.07-.083.017-.032.027-.068.03-.104z" fill="#0066b1"/><path d="m137 107.7c.011.062.041.12.084.167.044.046.101.078.163.093.106 0 .193-.154.186-.26-.006-.107-.113-.234-.226-.214-.055.005-.106.029-.143.069-.038.039-.061.09-.064.145z" fill="#0066b1"/><path d="m137.653 112.04c.066-.167.106-.354-.1-.467-.117-.045-.247-.043-.362.006-.116.048-.208.139-.258.254.08.107.153.293.293.38s.353.02.427-.173z" fill="#0066b1"/><path d="m135.693 110.733c.16.105.35.154.54.14.111.032.225.05.34.053.06-.1.12-.193.167-.286.057-.039.11-.081.16-.127-.031-.192-.083-.379-.153-.56-.061-.138-.166-.251-.299-.321-.134-.071-.287-.094-.435-.066-.168.038-.317.133-.422.269-.105.137-.158.306-.151.478.012.083.04.164.083.236.044.072.102.135.17.184z" fill="#0066b1"/><path d="m140.786 99.9467-.26-.2533-.193.28c.151.0316.307.022.453-.0267z" fill="#0066b1"/><path d="m130.133 110.227.106.113c.032-.035.057-.076.074-.12.003-.044.003-.089 0-.133z" fill="#0066b1"/><path d="m141.639 105.426c.071-.028.135-.072.185-.129.051-.056.088-.124.109-.197.013-.055.015-.112.005-.167-.009-.056-.029-.109-.059-.157-.029-.047-.068-.089-.114-.121-.046-.033-.097-.056-.152-.069-.32-.093-.54.22-.6.4s.28.534.626.44z" fill="#0066b1"/><path d="m130.354 110.42.32.32c-.014-.08-.053-.153-.11-.211-.057-.057-.131-.095-.21-.109z" fill="#0066b1"/><path d="m138.453 110.1c-.028-.154-.092-.3-.185-.426-.094-.126-.215-.229-.355-.3-.064-.017-.13-.017-.194 0h-.04c-.076.044-.14.106-.188.18-.047.074-.077.159-.085.246-.087.394 0 .607.287.667.386.113.706-.053.76-.367z" fill="#0066b1"/><path d="m136.626 112.347c.147-.033.407-.16.393-.287-.018-.071-.052-.138-.101-.194-.049-.055-.11-.098-.179-.126-.106 0-.433.114-.453.24-.02.127.273.38.34.367z" fill="#0066b1"/><path d="m143.66 111.333h-.08c-.067-.137-.184-.242-.327-.293-.236-.039-.479 0-.692.111-.212.11-.383.286-.488.502-.037.209.003.425.114.607.11.181.283.316.486.38.206.05.423.023.61-.076.187-.1.331-.265.403-.464.034-.147.034-.3 0-.447.023-.012.041-.031.054-.053.017-.021.03-.047.035-.074.006-.027.004-.055-.003-.081-.008-.027-.023-.051-.042-.07-.02-.02-.044-.034-.07-.042z" fill="#0066b1"/><path d="m142.993 108.993c.011-.074.005-.149-.018-.219-.024-.071-.063-.135-.116-.188-.05-.039-.109-.064-.172-.075-.063-.01-.127-.005-.188.015-.253.147-.253.38-.113.667.233.02.493.093.607-.2z" fill="#0066b1"/><path d="m141.713 112.893c.047.088.124.156.217.192.093.035.196.036.289.002.207-.1.214-.254 0-.667-.067-.021-.138-.023-.207-.007-.068.015-.132.049-.183.097s-.089.109-.11.176c-.02.067-.022.139-.006.207z" fill="#0066b1"/><path d="m144.666 104.886c-.078-.036-.163-.055-.25-.055-.086 0-.171.019-.25.055-.088-.075-.197-.121-.313-.133-.287 0-.367.153-.387.393-.009.126-.029.251-.06.374-.005-.031-.018-.059-.038-.082-.02-.024-.046-.042-.075-.052-.021-.008-.044-.012-.067-.011s-.046.007-.066.017c-.021.01-.039.024-.054.041-.015.018-.026.038-.033.06 0 .093-.053.28.087.353h.06c-.167.24-.36.48-.554.727-.9-.253-1.173-.093-1.186.747-.014.104 0 .21.038.308.039.097.102.184.184.25.081.067.178.112.281.131.104.019.21.011.31-.023.161-.053.315-.124.46-.213.247.187.473.48.86.467l.327-.314c.42.067.806-.046.873-.28.023-.064.029-.134.017-.202-.011-.068-.04-.131-.084-.184.12-.232.161-.496.118-.754-.043-.257-.169-.493-.358-.673-.053-.051-.096-.112-.126-.18.098.077.222.113.346.1.22-.1.174-.733-.06-.867z" fill="#0066b1"/><path d="m147.507 106.726h.08l-.4-.4c-.002.094.029.185.087.258.059.073.141.123.233.142z" fill="#0066b1"/><path d="m146.846 106-.94-.934-.066.094c-.029.053-.053.109-.074.166-.153.387-.046.614.354.727.116.051.243.073.369.063.127-.009.25-.049.357-.116z" fill="#0066b1"/><path d="m146.866 107.747c.017-.076.018-.153.005-.229-.014-.076-.042-.149-.083-.214-.042-.065-.096-.121-.159-.165s-.134-.076-.21-.092c-.048-.014-.099-.018-.149-.012-.051.006-.099.023-.143.048-.043.026-.081.06-.111.101s-.052.087-.063.136c-.049.101-.06.215-.034.323.027.108.091.204.18.27.061.041.13.069.202.082.071.014.145.013.217-.003.071-.015.139-.045.199-.087s.11-.096.149-.158z" fill="#0066b1"/><path d="m140.547 109.333c.17.022.342-.013.489-.1.148-.087.262-.221.324-.38.031-.159.003-.324-.08-.464-.082-.14-.212-.245-.366-.296-.083-.02-.17-.021-.253-.002-.083.018-.161.056-.227.109.048-.092.081-.191.1-.294l.12-.053c.42-.207.56-.467.446-.813-.059-.182-.188-.332-.358-.419-.169-.088-.366-.105-.548-.048-.437.125-.883.214-1.334.267-.36 0-.773.3-.746.553.026.253.42.407.533.367.287-.1.393.08.56.2-.003.031-.003.062 0 .093.035.128.106.243.204.333.098.089.219.15.349.174.089.015.181.01.267-.015.087-.026.167-.071.233-.132-.126.467 0 .867.287.92z" fill="#0066b1"/><path d="m141.72 103.253c-.374-.147-.494.213-.7.453.193.32.366.567.733.447.05-.01.097-.03.138-.059.042-.029.077-.066.104-.11.027-.043.045-.091.052-.141.008-.05.005-.101-.007-.15-.045-.183-.16-.341-.32-.44z" fill="#0066b1"/><path d="m141.693 101.02c.043.233.157.447.327.613.065.061.151.094.24.094s.174-.033.24-.094l-.794-.793c-.017.058-.022.12-.013.18z" fill="#0066b1"/><path d="m143.113 103.567c-.139-.017-.28.017-.395.097-.116.08-.198.2-.231.336-.017.077-.016.157.001.234s.05.15.097.213c.048.063.108.115.177.153s.146.061.225.067c.125.009.249-.031.345-.112s.156-.197.168-.322c.053-.306-.167-.66-.387-.666z" fill="#0066b1"/><path d="m143.899 103.227c.047.006.094.006.14 0l-.533-.534c-.007.047-.007.094 0 .14.003.104.046.202.119.275s.171.115.274.119z" fill="#0066b1"/><path d="m134.973 107.933c-.16.367-.086.667.18.78.173.032.351.014.514-.053.052.244.195.458.4.6.4.207.92.153.973-.247.023-.361-.081-.72-.293-1.013-.14-.26-.507-.167-.374-.487.114-.255.144-.54.087-.813.133-.017.26-.066.37-.143s.2-.178.263-.297c.024-.117.008-.239-.045-.347-.053-.107-.14-.194-.248-.246-.44-.154-.8-.074-.893.206-.032.16-.003.326.08.467-.157-.007-.312.032-.446.113-.134.08-.241.199-.308.34-.094-.188-.252-.337-.446-.42-.347.334-.7.66-1.06.98.028.123.091.235.181.324.089.088.202.149.325.176.193.001.382-.047.551-.139s.312-.226.416-.387c.019.072.053.14.1.2-.079.042-.149.1-.205.17s-.097.15-.122.236z" fill="#0066b1"/><path d="m138.393 102.54c.313.666.613.766 1.186.466.434-.22.874-.426 1.334-.633.533.84.666.867 1.18.327-.007-.142-.058-.277-.147-.388-.089-.11-.21-.189-.347-.226.048-.035.085-.084.107-.14.02-.046.03-.096.029-.146s-.012-.1-.034-.145c-.021-.045-.052-.086-.09-.118-.039-.033-.083-.057-.132-.071-.04-.02-.083-.031-.128-.033-.045-.003-.089.004-.131.02s-.08.04-.112.071c-.033.031-.058.068-.075.109-.44-.747-1.034-.72-1.687.067-.075-.016-.149-.039-.22-.067-.233.3-.473.593-.713.887z" fill="#0066b1"/><path d="m144.16 116.447c.064-.116.088-.25.069-.382-.019-.131-.081-.252-.176-.345-.128-.131-.268-.25-.42-.353-.01-.033-.024-.064-.04-.094 0-.073 0-.153 0-.233-.003-.181-.06-.356-.163-.505-.103-.148-.248-.262-.417-.328-.177-.039-.36-.033-.534.019-.174.051-.332.145-.459.274-.092.105-.142.24-.14.38.001.131.033.261.095.377.061.116.15.215.258.29 0 .326.2.573.607.633.047.003.093.003.14 0v.107c0 .346.127.473.46.513.142.027.289.007.419-.056.13-.064.236-.168.301-.297z" fill="#0066b1"/><path d="m146.373 114c-.167 0-.587.073-.614.24-.026.167.314.393.46.473.147.08.394-.113.414-.326.02-.214-.047-.387-.26-.387z" fill="#0066b1"/><path d="m147.52 109.5c-.094.093-.294.207-.367.373-.073.167.127.514.407.554.054.006.11.002.163-.014.053-.015.103-.041.145-.076.043-.035.079-.078.105-.126.026-.049.042-.103.047-.158 0-.138-.051-.271-.143-.373-.093-.103-.22-.167-.357-.18z" fill="#0066b1"/><path d="m145.393 118.54c-.085.032-.154.096-.193.178s-.044.176-.013.262c.086.34.353.38.666.394.147-.26.36-.48.127-.74-.073-.081-.17-.134-.277-.151s-.216.003-.31.057z" fill="#0066b1"/><path d="m146.86 116.807c-.087 0-.28-.053-.347.087-.066.14.12.26.167.26.05 0 .1-.01.147-.031.046-.021.087-.051.12-.089 0 0-.04-.214-.087-.227z" fill="#0066b1"/><path d="m145.006 112c.153.06.347 0 .327-.22s-.06-.52-.227-.553c-.167-.034-.36.333-.38.466-.02.134.16.307.28.307z" fill="#0066b1"/><path d="m145.333 114.707c.058-.034.107-.083.14-.141.034-.059.051-.125.051-.192 0-.068-.017-.134-.051-.192-.033-.059-.082-.108-.14-.142-.353-.12-.573.287-.5.487.05.083.127.146.218.179s.191.033.282.001z" fill="#0066b1"/><path d="m148.833 113.646c-.108-.015-.218.006-.312.061-.094.054-.167.139-.208.239-.019.119 0 .241.055.348s.144.193.252.246c.109.024.224.005.32-.053.095-.059.165-.152.193-.261.031-.116.017-.24-.038-.347-.056-.107-.149-.19-.262-.233z" fill="#0066b1"/><path d="m152.626 111.953c.01-.058.01-.116 0-.174l-.513-.52c-.32-.066-.527.04-.593.32-.1.4.119.88.433.94.16-.004.313-.062.435-.165.123-.103.207-.245.238-.401z" fill="#0066b1"/><path d="m152.666 114.726c-.052-.017-.107-.024-.161-.02-.055.005-.108.02-.157.046-.048.025-.091.06-.126.103s-.06.092-.076.144c-.022.109-.005.222.049.319.054.096.141.17.245.208.051.021.106.03.162.028.055-.003.109-.017.159-.042.049-.026.092-.061.127-.105.034-.043.059-.094.072-.148.021-.109.004-.222-.05-.32-.053-.097-.14-.173-.244-.213z" fill="#0066b1"/><path d="m150.667 116.367c-.069-.071-.151-.128-.242-.167s-.189-.059-.288-.059c-.1 0-.198.02-.289.059s-.173.096-.241.167c-.076.12-.112.261-.103.402.01.142.065.276.156.385.074.111.187.19.318.22.13.03.267.008.382-.06.213-.1.667-.187.707-.527s-.274-.307-.4-.42z" fill="#0066b1"/><path d="m149.866 111.087c.008-.113-.028-.225-.1-.313s-.174-.146-.287-.161c-.154-.003-.305.046-.426.141-.122.095-.207.229-.241.379.048.144.144.268.271.35.127.083.279.119.429.104.111-.024.209-.089.274-.182.066-.092.094-.206.08-.318z" fill="#0066b1"/><path d="m145.12 110.667c.18.016.36-.028.513-.124.153-.097.27-.24.334-.41.066-.26-.214-.606-.547-.666-.433-.094-.667 0-.767.346-.03.174-.001.354.084.509s.22.276.383.345z" fill="#0066b1"/><path d="m148.666 109.333c.118.024.24.001.341-.064.101-.064.172-.166.199-.283.018-.128-.016-.259-.093-.363-.077-.105-.192-.175-.32-.197-.118-.012-.237.02-.333.09s-.163.174-.187.29c-.005.12.031.238.103.334.071.096.174.164.29.193z" fill="#0066b1"/><path d="m138.886 117.133c-.101-.039-.21-.056-.318-.049s-.214.037-.309.089c-.024-.079-.07-.149-.132-.202-.063-.053-.14-.087-.221-.098-.314-.06-.447.2-.594.453.06.087.121.187.181.267l.093.087c.05.044.112.073.178.084.066.01.134.002.195-.024l.087-.054c.025.081.064.158.113.227.006.031.018.06.037.086.018.025.042.046.07.061.083.111.196.197.326.246.057.019.117.025.177.019.059-.005.117-.023.169-.052.053-.028.099-.067.136-.114.036-.048.063-.102.078-.159.074-.152.087-.325.037-.486-.049-.161-.158-.297-.303-.381z" fill="#0066b1"/><path d="m137.793 115.454c.007-.056.007-.112 0-.167.25-.061.468-.216.606-.433.174-.267 0-.567-.04-.88.05-.061.086-.131.107-.207.019-.125-.013-.251-.087-.353-.033-.667-.613-.86-1.173-.86-.215.002-.424.074-.593.206-.059.051-.112.107-.16.167-.132-.135-.302-.226-.487-.26-.353-.067-.593.147-.667.593-.009.142.027.284.104.404s.19.212.323.263c.164.007.328-.029.473-.107-.055.088-.093.185-.11.287s-.014.206.01.307c.071.203.186.388.337.542.151.153.334.271.537.344v.04c-.04.174.346.374.506.427s.3-.08.314-.313z" fill="#0066b1"/><path d="m135.86 112.5c.097-.212.121-.452.067-.679-.053-.228-.18-.432-.361-.581-.169-.104-.371-.141-.567-.106-.195.036-.371.143-.493.3-.08.08-.141.176-.179.283-.038.106-.051.22-.04.332.012.112.048.221.107.317.058.097.138.179.232.241.18.145.409.215.638.195.23-.02.444-.128.596-.302z" fill="#0066b1"/><path d="m134.793 110.153c.175-.116.3-.294.351-.498s.025-.419-.074-.604c-.099-.186-.263-.328-.461-.399s-.415-.066-.609.015c-.313.113-.367.666-.227 1.093.093.178.248.317.436.389.188.073.396.074.584.004z" fill="#0066b1"/><path d="m134.326 114.133c-.072.017-.14.049-.2.093l.973.974c.035-.129.053-.261.054-.394-.035-.192-.138-.366-.289-.489-.152-.123-.343-.189-.538-.184z" fill="#0066b1"/><path d="m133.02 109.14c-.094-.035-.193-.052-.293-.048s-.199.029-.289.072-.171.103-.237.179c-.066.075-.117.162-.148.257-.14.36.187.88.667 1.06.085.029.174.041.264.034.089-.007.176-.031.256-.072s.15-.098.207-.167c.058-.069.1-.149.126-.235.173-.64.013-.78-.553-1.08z" fill="#0066b1"/><path d="m144 120.36c-.247-.14-.627.067-.813.44-.042.114-.044.239-.003.353.04.115.119.212.223.274.136.061.289.075.434.039.145-.037.274-.121.366-.239.063-.147.077-.311.04-.467-.037-.157-.124-.297-.247-.4z" fill="#0066b1"/><path d="m132.666 110.96c-.099-.031-.205-.041-.309-.029-.103.011-.204.045-.294.097-.09.053-.169.124-.23.209-.061.084-.105.181-.127.283-.013.088-.013.178 0 .266l1.054 1.047c.226-.033.32-.207.413-.387.066-.106.118-.22.153-.34.034-.052.061-.108.08-.166.1-.427-.2-.84-.74-.98z" fill="#0066b1"/><path d="m141.093 116.706c.059-.193.045-.401-.038-.585-.084-.184-.231-.331-.415-.415-.07-.026-.145-.038-.221-.036-.075.003-.149.021-.217.052-.068.032-.13.077-.18.133-.051.055-.09.12-.115.191-.078.162-.102.343-.07.52.033.176.12.337.25.46.178.075.377.085.562.026.184-.059.342-.181.444-.346z" fill="#0066b1"/><path d="m131.286 109.38c-.05.045-.091.099-.12.16-.028.06-.044.126-.046.194 0 .133-.074.253 0 .386.073.134.293.094.406-.073.097-.162.128-.356.087-.54-.008-.034-.024-.065-.047-.09-.023-.026-.051-.046-.083-.059-.032-.012-.067-.017-.101-.013-.035.004-.067.016-.096.035z" fill="#0066b1"/><path d="m141.553 118.753c-.136-.062-.289-.078-.435-.047-.147.031-.279.108-.378.22-.061.171-.062.357-.003.528.06.171.176.316.329.412.163.051.339.042.496-.024.157-.065.286-.184.364-.336.154-.306.027-.56-.373-.753z" fill="#0066b1"/><path d="m141.38 113.16c-.168-.035-.344-.01-.496.068-.153.079-.274.208-.344.365-.034.127-.018.261.044.376.062.116.165.203.289.244.159.029.322 0 .461-.082.138-.082.242-.212.293-.365.025-.055.038-.116.039-.177s-.01-.122-.033-.179c-.023-.056-.057-.108-.101-.151-.043-.043-.095-.077-.152-.099z" fill="#0066b1"/><path d="m140.36 111.62c.054-.008.106-.026.153-.054.054-.029.101-.07.137-.12.037-.049.063-.106.076-.166l.1-.114c.247.18.48.394.747.094.083-.075.136-.178.151-.289s-.011-.224-.071-.318c-.053-.073-.16-.173-.233-.167l-.24.04c.039-.048.069-.103.087-.163s.024-.123.017-.185c-.007-.063-.026-.123-.057-.177-.03-.055-.071-.103-.121-.141-.07-.061-.152-.107-.24-.135-.089-.027-.183-.036-.275-.025-.092.01-.181.04-.261.087s-.15.111-.204.186c-.088.108-.147.236-.173.373-.06 0-.12.013-.175.037s-.104.059-.145.103c-.093.178-.127.38-.097.578s.122.381.264.522c.07.078.168.126.273.132.104.006.207-.029.287-.098z" fill="#0066b1"/><path d="m139.193 114.48c-.193.174-.393.367-.26.667.019.053.05.101.089.141s.086.072.138.093c.053.02.109.03.165.028.056-.003.111-.017.161-.042.075-.037.139-.091.19-.156.051-.066.086-.143.104-.224 0-.32-.267-.467-.587-.507z" fill="#0066b1"/><path d="m175.172 25.7409c1.935-2.0964 2.189-5.0108.566-6.5095s-4.508-1.0142-6.444 1.0821c-1.936 2.0964-2.189 5.0108-.566 6.5095 1.623 1.4988 4.508 1.0143 6.444-1.0821z" fill="#294492"/><path d="m155.779 18.3999c.8 2.2066 6.88 2.6333 9.067 2.72.237.0093.472-.0414.685-.1475.212-.106.394-.264.528-.4592l.267-.3867c.132-.1934.213-.4167.236-.6497.024-.2329-.012-.4679-.103-.6836-.853-2-3.426-7.54-5.766-7.5-2.134.04-3.827 2.62-3.827 2.62s-1.813 2.4867-1.087 4.4867z" fill="#7de2d1"/><path d="m159.213 12.0533c-.246 2.3267 5.034 5.38 6.96 6.4067.206.1086.435.1653.667.1653s.461-.0567.667-.1653l.406-.2334c.216-.1126.399-.2793.531-.4838s.209-.4398.223-.6828c.106-2.1867.22-8.28002-1.907-9.27335-1.933-.9-4.587.66666-4.587.66666s-2.74 1.48-2.96 3.59999z" fill="#7de2d1"/><path d="m160.666 11.2737c-.491.0173-.973.1473-1.406.38-.035.1312-.059.265-.074.4-.26 2.4133 5.42 5.5933 7.154 6.5133-.907-2.1667-3.38-7.3333-5.674-7.2933z" fill="#42d4c6"/><path d="m188.206 27.78c-.8-2.2-6.88-2.6667-9.066-2.7133-.237-.01-.473.0404-.685.1465-.212.1062-.394.2645-.528.4601l-.261.3867c-.133.1925-.216.4157-.24.6487-.025.2331.01.4686.1.6846.86 2 3.427 7.5467 5.774 7.5067 2.133-.04 3.826-2.62 3.826-2.62s1.807-2.4933 1.08-4.5z" fill="#7de2d1"/><path d="m184.78 34.1331c.247-2.3333-5.04-5.38-6.967-6.4133-.205-.1103-.434-.168-.666-.168-.233 0-.462.0577-.667.168l-.407.2266c-.215.1139-.396.2822-.526.4881-.13.2058-.204.4421-.214.6853-.113 2.1866-.22 8.2866 1.907 9.2733 1.933.9 4.587-.6667 4.587-.6667s2.726-1.4733 2.953-3.5933z" fill="#7de2d1"/><path d="m177.619 27.6201c.933 2.16 3.413 7.3333 5.68 7.2933.492-.0195.974-.1518 1.407-.3866.031-.1297.056-.261.073-.3934.253-2.4133-5.447-5.5999-7.16-6.5133z" fill="#42d4c6"/><path d="m57.1063 44.1738h-56.773292v7.4334h56.773292z" fill="#00bfb3"/><path d="m85.1598 61.3262h-60.0133v7.4333h60.0133z" fill="#ff957d"/><path d="m63.333 68.7597h21.8333v-7.4267h-19.96c-.8493 2.4133-1.4763 4.8992-1.8733 7.4267z" fill="#fa744e"/><path d="m153.753 43.6996c-.332.0022-.656-.0968-.929-.2838-.274-.187-.484-.4531-.602-.7627-.118-.3095-.139-.6478-.059-.9695.079-.3217.254-.6115.503-.8306.9-.7867 1.833-1.6267 2.767-2.4867.323-.2616.733-.3902 1.147-.3598.415.0304.802.2176 1.083.5234.281.3059.435.7075.431 1.1229-.004.4155-.167.8137-.455 1.1135-.96.88-1.906 1.7333-2.826 2.5333-.293.2577-.67.3998-1.06.4zm8.353-7.82c-.322-.0001-.637-.0957-.904-.2749-.268-.1792-.477-.4337-.599-.7315-.123-.2977-.155-.6252-.091-.9409.063-.3158.219-.6055.447-.8327 1.6-1.5933 2.594-2.6666 2.6-2.6666.302-.2883.703-.4496 1.12-.4506s.819.1584 1.122.4452.484.6791.506 1.0957c.022.4167-.116.8259-.388 1.143-.04.04-1.033 1.0867-2.666 2.7133-.301.3117-.714.4914-1.147.5z" fill="#294492"/><path d="m54.22 21.241-12.7138 12.7138c-1.4866 1.4866-1.4866 3.8968 0 5.3834l12.7138 12.7138c1.4865 1.4866 3.8968 1.4866 5.3834 0l12.7138-12.7138c1.4866-1.4866 1.4866-3.8968 0-5.3834l-12.7138-12.7138c-1.4866-1.4866-3.8968-1.4866-5.3834 0z" fill="#f04e98"/><path d="m31.2467 47.473c.0298-.077.0439-.1591.0416-.2416-.0024-.0825-.0212-.1637-.0553-.2388-.0342-.0751-.083-.1427-.1436-.1987s-.1318-.0994-.2094-.1276c-.26-.1266-.5733.0534-.74.42-.0367.055-.0611.1174-.0712.1827-.0101.0654-.0058.1322.0126.1957s.0505.1223.0941.1721c.0435.0498.0974.0894.1579.1162.1634.0358.3334.0288.4934-.0202.1599-.0491.3046-.1385.4199-.2598z" fill="#01ada1"/><path d="m21.22 51.2334c-.0534.1066.12.2533.2.3066.041.017.0868.0182.1286.0034.0418-.0147.0768-.0444.098-.0834.0143-.05.0151-.1029.0022-.1532-.0129-.0504-.039-.0964-.0755-.1334-.0533-.04-.3-.0467-.3533.06z" fill="#01ada1"/><path d="m23.58 46.3801c.12.0533.2467-.1333.2934-.2133.0097-.0448.006-.0914-.0106-.1341s-.0454-.0795-.0828-.1059c-.08 0-.2133.0733-.2733.1466-.06.0734-.04.2534.0733.3067z" fill="#01ada1"/><path d="m26.0334 50.26c-.26-.04-.52-.12-.7734-.18-.0436-.0417-.0964-.0724-.1542-.0896s-.1189-.0204-.1782-.0093c-.0592.011-.115.0361-.1627.073s-.0859.0846-.1115.1392c-.0604.1145-.074.248-.0379.3723.036.1244.1189.2299.2312.2944.1431.0888.2658.2069.36.3466.0428.0932.1047.1763.1816.244.077.0678.1672.1187.2651.1494h.2866c.1352-.0798.249-.1913.3315-.3249.0824-.1335.1311-.2852.1419-.4418-.02-.2467-.0267-.52-.38-.5733z" fill="#01ada1"/><path d="m27.2131 47.5664c-.0424-.0258-.0905-.0408-.1401-.0435s-.0991.007-.144.0281c-.045.0212-.0839.0531-.1135.0931-.0295.0399-.0487.0865-.0557.1357.006.1437.0353.2856.0866.42.0573.0211.1183.0306.1793.0279s.1209-.0175.1761-.0435c.0552-.0261.1047-.0629.1456-.1083.0408-.0454.0722-.0985.0924-.1561.06-.2067-.0734-.2934-.2267-.3534z" fill="#01ada1"/><path d="m32.9259 47.2002c.1079.0564.2337.0677.35.0315.1162-.0363.2133-.1171.27-.2248.0031-.0399.0031-.0801 0-.12.0629-.0213.1199-.0571.1662-.1046.0464-.0475.0808-.1054.1005-.1687.005-.0637-.0051-.1277-.0294-.1868-.0243-.059-.0622-.1115-.1106-.1532-.065-.0291-.1355-.0441-.2067-.0441s-.1416.015-.2066.0441c-.0385.0311-.0702.0696-.0934.1133-.1037-.0148-.2096.0021-.3035.0485-.094.0464-.1718.1201-.2232.2115-.0303.1114-.0177.2301.0354.3326s.1428.1813.2513.2207z" fill="#01ada1"/><path d="m29.386 50.9335c-.1003-.0441-.2137-.0479-.3167-.0106-.103.0372-.1877.1126-.2367.2106-.0428.0734-.0619.1583-.0548.243.0072.0847.0403.1651.0948.2303h.7467c.0304-.0311.0552-.0671.0733-.1066.0174-.0574.0233-.1176.0172-.1772-.006-.0596-.0238-.1174-.0523-.1701s-.0672-.0992-.1138-.1369-.1002-.0657-.1577-.0825z" fill="#01ada1"/><path d="m29.1995 49.0197c.06-.2066-.0733-.2866-.2266-.3533-.1534-.0667-.4334 0-.4534.2133.0048.1439.0341.286.0867.42.0571.0231.1184.0341.18.0324.0616-.0018.1222-.0163.1779-.0426s.1054-.0638.1459-.1103c.0406-.0464.071-.1007.0895-.1595z" fill="#01ada1"/><path d="m27.1864 45.9996c-.0193.0534-.0206.1116-.0038.1657.0169.0542.051.1013.0971.1343.1.0467.24-.0533.28-.12.04-.0666.0467-.3266-.0533-.3666s-.2933.0933-.32.1866z" fill="#01ada1"/><path d="m23.2465 47.5599c-.3733-.04-.48.4467-.3533.6667s.0534.1733-.0933.4133-.22.6667.26.7734c.2238.0329.4519-.0165.642-.1391.19-.1226.3291-.3101.3913-.5276.0311-.2675-.0384-.5371-.1948-.7563-.1565-.2193-.3888-.3727-.6519-.4304z" fill="#01ada1"/><path d="m43.5731 46.7c-.2866-.6666-.8266-.2733-.9733-.4466-.1467-.1734-.18-.6334-.3733-.9134-.0483-.0973-.1176-.1827-.2029-.2499-.0853-.0673-.1845-.1148-.2904-.139s-.2159-.0246-.322-.0012c-.106.0235-.2056.0702-.2914.1368-.0901.0659-.1656.1497-.2216.2462-.0561.0965-.0915.2036-.104.3145-.0126.111-.0019.2233.0312.3299.0331.1065.0879.2051.1611.2894-.0159.023-.0293.0476-.04.0733.0004.178.0548.3516.156.498s.2443.2587.4106.322c.1311.0345.2689.0345.4 0-.0432.0775-.0658.1647-.0658.2534s.0226.1759.0658.2533c.2267.5734.86.7267 1.1134.3667.2533-.36.0733-.6667.1533-.7534.08-.0866.52-.2599.3933-.58z" fill="#01ada1"/><path d="m45.0998 47.4797c-.0596-.0098-.1204-.0098-.18 0-.0141-.0845-.0506-.1637-.1057-.2293-.0551-.0657-.1268-.1154-.2076-.144-.161-.0145-.3222.0256-.4577.1139-.1354.0883-.2372.2196-.289.3728-.0147.0535-.018.1096-.0095.1645s.0285.1074.0587.154c.0303.0466.07.0863.1167.1164.0467.0302.0992.0501.1541.0584.1001.03.2067.03.3067 0-.0153.0946-.0048.1916.0304.2808s.0938.1672.1696.2259c.4267.2333.7867-.1267.9667-.32s-.1467-.7067-.5534-.7934z" fill="#01ada1"/><path d="m45.7068 44.6205c-.0669-.1356-.1779-.2444-.3148-.3086s-.2915-.08-.4386-.0447c-.12 0-.2333.04-.36.06l-.12-.1534h-1.7c0 .04.06.08.0934.1134.0322.0896.0796.173.14.2466-.1334.46.1333.7067.5.9067-.0934.24-.32.4667 0 .7067.0765.067.1749.104.2766.104.1018 0 .2001-.037.2767-.104.3-.2267.1267-.4867 0-.74l.3533-.3267.4467.44c.1645-.0297.3253-.0767.48-.14.0923-.0574.1681-.1378.22-.2333.0864-.0604.1572-.1404.2067-.2334.0165-.0498.0197-.1032.0092-.1547-.0106-.0514-.0344-.0992-.0692-.1386z" fill="#01ada1"/><path d="m34.7133 46.9728c.0647-.0417.1178-.0989.1547-.1665.0368-.0675.0561-.1432.0561-.2202 0-.0769-.0193-.1526-.0561-.2202-.0369-.0675-.09-.1247-.1547-.1664-.0852-.0413-.1787-.0628-.2734-.0628-.0946 0-.1881.0215-.2733.0628-.0662.053-.1184.1214-.1521.1993-.0337.0778-.0479.1628-.0412.2473.0223.0735.0602.1414.1111.1989s.1136.1033.1839.1343c.0703.0311.1464.0465.2232.0454s.1525-.0188.2218-.0519z" fill="#01ada1"/><path d="m40.98 44.7672c.0867-.14-.0533-.4867-.24-.5934h-.22c-.0765.0331-.1414.0881-.1865.1582-.0451.07-.0684.1519-.0668.2352.04.2466.6066.3666.7133.2z" fill="#01ada1"/><path d="m38.2197 47.5797c-.1266.36 0 .6666.2467.7666.1953.0463.4003.0269.5835-.055.1831-.0819.3342-.2219.4298-.3983.0076-.1454-.0326-.2892-.1144-.4096-.0819-.1204-.2009-.2107-.3389-.2571-.0752-.0375-.1577-.058-.2417-.0601s-.1674.0143-.2443.048c-.077.0337-.1456.0839-.201.1471-.0554.0631-.0963.1377-.1197.2184z" fill="#01ada1"/><path d="m35.4 44.1738h-.5533c.0466.1334.1.24.16.2667s.3266-.0667.3933-.2667z" fill="#01ada1"/><path d="m38.5202 47.0069c.44-.5667.7067-.1333 1.1533-.4333.0621-.0542.116-.1171.16-.1867.0628-.091.0982-.198.1022-.3084.004-.1105-.0236-.2198-.0797-.315-.056-.0953-.1381-.1726-.2366-.2227-.0984-.0502-.2092-.0712-.3192-.0606-.3233.0076-.644.0592-.9533.1534-.1713.0841-.3114.2205-.4001.3895-.0886.169-.1213.3617-.0933.5505.0334.2066.2734.6066.6667.4333z" fill="#01ada1"/><path d="m39.4998 45.3331c.0448.0226.0943.0344.1445.0344.0502-.0001.0997-.0119.1445-.0347.0448-.0227.0836-.0556.1133-.0961s.0494-.0874.0577-.137c.0124-.0778.0093-.1573-.009-.2339s-.0515-.1489-.0977-.2127c-.0933-.0867-.5333-.0467-.6067.14-.0367.1052-.0307.2207.0167.3216.0473.1009.1323.1793.2367.2184z" fill="#01ada1"/><path d="m31.5731 45.8396c.26-.08.2933-.2933.18-.7866-.2534-.0467-.54-.1934-.7267.14-.0594.0944-.08.2081-.0576.3174.0223.1092.0859.2057.1776.2692.0623.0408.1329.0673.2066.0777.0738.0103.1489.0043.2201-.0177z" fill="#01ada1"/><path d="m48.153 50.2532c-.0553-.0249-.1152-.0383-.1759-.0393s-.121.0104-.1771.0335c-.0562.0231-.107.0574-.1495.1008s-.0756.095-.0975.1517c-.0171.0524-.0233.1079-.0179.1629.0053.055.0219.1083.0489.1565.0269.0482.0635.0903.1075.1237.0441.0334.0945.0573.1482.0702.1158.0379.2419.0283.3507-.0267.1087-.055.1912-.1509.2293-.2666.0161-.0482.0219-.0992.017-.1497-.005-.0505-.0206-.0994-.0457-.1434-.0252-.0441-.0594-.0824-.1004-.1123s-.0879-.0508-.1376-.0613z" fill="#01ada1"/><path d="m45.0527 49.6804c-.033-.0068-.067-.0068-.1 0-.0908-.206-.2582-.3686-.4667-.4534-.1719-.0213-.3456.0213-.4881.1196-.1426.0983-.2441.2456-.2852.4138-.01.0529-.01.1071 0 .16-.068.1786-.0997.369-.0934.56.0467.1267.2867.4.0667.5533-.22.1534-.56.14-.6667.4067l-.04.0933c-.0478-.0482-.1076-.0827-.1733-.1-.1752-.0173-.3502.0355-.4867.1467h1.9134c-.0299-.1069-.0299-.2198 0-.3267.0733-.1533.2133-.3.4333-.1666.22.1333.62.0533.8667-.42.2466-.4734-.0934-1.0401-.48-.9867z" fill="#01ada1"/><path d="m47.1528 50.7266c-.2266-.06-.58.2334-.6266.52-.0151.0693-.0077.1415.0211.2062s.0774.1186.1389.1538h.8733c.0358-.0345.0612-.0784.0733-.1266.013-.1607-.0268-.3213-.1135-.4573-.0866-.136-.2153-.2399-.3665-.2961z" fill="#01ada1"/><path d="m45.46 51.6064h1.1333c-.0941-.0882-.2053-.1563-.3267-.2-.1372-.063-.2917-.0774-.4383-.0411-.1465.0363-.2764.1213-.3683.2411z" fill="#01ada1"/><path d="m47.7127 49.3931c.12-.4533-.1133-.7866-.6666-.9133-.1193-.0186-.2407-.0186-.36 0-.0881-.007-.1765.0104-.2553.0504s-.145.101-.1914.1763c-.0799.1005-.1389.2162-.1733.34-.0189.1118-.0143.2263.0134.3363s.0779.2131.1474.3027c.0696.0896.157.1638.2566.2179.0997.0541.2096.0869.3226.0964.1988.0247.3999-.023.5664-.1344s.2873-.2791.3402-.4723z" fill="#01ada1"/><path d="m51.6133 49.7136c.0393.0088.0802.0086.1194-.0006.0393-.0093.076-.0273.1072-.0527l-1.0733-1.0734c-.117.1033-.2103.2306-.2733.3734-.0376.0771-.0582.1613-.0605.2471-.0022.0857.0139.171.0474.25.0335.0789.0835.1498.1467.2078s.1382.1018.2197.1284c.122.0615.2592.0866.3951.0725.1359-.0142.2649-.0671.3716-.1525z" fill="#01ada1"/><path d="m49.0793 51.1938c-.1123.0039-.2196.0476-.3027.1233-.083.0757-.1364.1786-.1506.2901h.8933c.0089-.0588.0034-.1188-.016-.1749s-.0522-.1067-.0955-.1473c-.0433-.0407-.0958-.0702-.153-.0861s-.1174-.0176-.1755-.0051z" fill="#01ada1"/><path d="m52.573 50.4c-.4-.0534-.8933.0333-1.04.2666-.025.0326-.039.0723-.04.1134.1134.38-.1866.52-.3933.7266-.0287.03-.0533.0637-.0733.1h1.2466c.0303-.0509.0708-.095.119-.1295.0482-.0344.103-.0584.161-.0705.0667.06.1334.1467.2.2h.58c.0388-.0103.0767-.0237.1134-.04.0336-.0254.0604-.0589.0779-.0973.0174-.0384.025-.0806.0221-.1227z" fill="#01ada1"/><path d="m49.1794 47.4132c-.0775-.0394-.1607-.0664-.2466-.08.0728.0122.1471.0122.22 0 .0832-.02.1607-.0587.2266-.1133l-1.5333-1.5334c-.0867.1867-.16.3467-.2333.36-.0734.0134-.6667-.4866-1.1867-.28-.2215.0959-.3976.2732-.492.4954-.0943.2221-.0996.472-.0147.698.1427.2641.3577.4822.6198.6286s.5605.2151.8602.198c.4267-.1133.44-.6666.56-.7133s.4734.2533.8867.2867c-.1399.0015-.2766.0422-.3945.1177-.1179.0754-.2122.1825-.2722.3089-.0806.1702-.1062.3613-.0733.5467-.0522.0724-.0906.1537-.1133.24-.0258.1295-.025.2628.0023.392.0274.1291.0807.2513.1568.3592s.1733.1992.2858.2683c.1124.0691.2378.1147.3684.1338.1222.0306.2493.0362.3738.0167.1244-.0196.2437-.0639.3506-.1305.107-.0665.1995-.1539.272-.2569.0726-.103.1237-.2195.1503-.3426.0358-.1117.0448-.2303.0263-.3461s-.0641-.2256-.133-.3206c0-.2056-.0642-.4061-.1838-.5735-.1195-.1673-.2883-.2931-.4829-.3598z" fill="#01ada1"/><path d="m42.1392 50c-.0031-.0131-.0031-.0268 0-.04.093-.0206.1781-.0673.2454-.1346s.114-.1525.1346-.2454c.0093-.121-.0297-.2407-.1084-.3331-.0786-.0925-.1906-.15-.3116-.1602-.0804-.0239-.1661-.0239-.2466 0-.1706-.0495-.3524-.0431-.5191.018-.1667.0612-.3095.174-.4076.322-.096-.0666-.2051-.112-.32-.1334-.0627-.0276-.1304-.042-.199-.042-.0685-.0001-.1363.0142-.199.0418s-.119.0679-.1652.1185c-.0463.0505-.0815.1102-.1035.1751-.0134.1776.0232.3555.1057.5134.0825.1578.2075.2895.361.3799.0736.0145.1493.0142.2228-.0008s.1432-.0444.2052-.0866.1151-.0962.156-.1591c.041-.0628.0691-.1331.0827-.2068.0663.0586.1403.108.22.1466.0661.0467.1412.0793.2204.0958.0793.0166.161.0167.2404.0005.0793-.0162.1544-.0485.2208-.0949.0663-.0464.1225-.1058.165-.1747z" fill="#01ada1"/><path d="m36.0801 51.6061h.7c-.0587-.0589-.1262-.1084-.2-.1467-.086-.0455-.1864-.0557-.2798-.0283s-.1724.0902-.2202.175z" fill="#01ada1"/><path d="m36.4398 49.227c.1134-.2934 0-.7067-.2133-.7934-.1983-.0593-.4113-.0451-.6.04-.0633-.0856-.1558-.1449-.26-.1666-.1022-.0191-.2079-.004-.3007.043s-.1675.1232-.2126.217c-.2677-.119-.5633-.1606-.8534-.12-.126.013-.2532-.0101-.3666-.0667-.0747-.069-.1661-.1174-.2651-.1404-.099-.0231-.2023-.02-.2998.0089-.0974.0289-.1857.0827-.2562.156-.0704.0733-.1206.1636-.1456.2622-.0373-.0448-.0855-.0793-.14-.1-.122-.011-.2441.021-.3451.0904-.101.0693-.1746.1717-.2082.2896-.0623.1282-.0713.2759-.0251.4108s.1439.246.2717.3092c.0578.0184.1187.025.179.0195.0604-.0056.119-.0231.1725-.0517.0534-.0286.1006-.0676.1387-.1148.0381-.0471.0664-.1014.0832-.1597.1199.096.262.1602.4133.1867.219.0113.4258.1041.58.26.1172.1107.2611.1891.4177.2277.1565.0385.3204.0359.4756-.0077.1581-.0367.3016-.1197.412-.2385.1105-.1188.183-.2679.208-.4282 0 0 0-.0533 0-.0867.0771.1836.2231.3296.4067.4067.2933.1133.5733-.0733.7333-.4533z" fill="#01ada1"/><path d="m37.6666 44.6667c-.0513-.0986-.1298-.1803-.2262-.2355s-.2066-.0816-.3176-.076c-.1109.0057-.2179.043-.3082.1077-.0904.0646-.1602.1539-.2013.2571-.1173.2395-.2046.4925-.26.7533-.0039-.0233-.015-.0449-.0317-.0616-.0167-.0168-.0383-.0278-.0617-.0317-.034-.0022-.0677.008-.0947.0289s-.0455.0509-.0519.0844c0 .04.0733.12.1067.1267.0364.0023.0722-.0096.0999-.0333.0022.0154.0022.0311 0 .0466-.0031.1916.0537.3794.1625.5372.1087.1577.264.2776.4442.3428.0827.0408.1779.0484.2659.0211.0881-.0273.1623-.0873.2074-.1677.2692-.4424.3971-.9564.3667-1.4734-.0239-.0794-.0574-.1555-.1-.2266z" fill="#01ada1"/><path d="m35.6132 51.2064c-.0625-.0258-.1295-.0389-.1971-.0384-.0677.0005-.1345.0145-.1966.0412-.0622.0267-.1183.0656-.1652.1143-.0469.0488-.0835.1065-.1077.1696-.0069.0375-.0069.0759 0 .1133h.94c-.0031-.0857-.0304-.1687-.0788-.2395s-.1158-.1265-.1946-.1605z" fill="#01ada1"/><path d="m39.0461 49.8604c-.1643-.0983-.358-.1354-.547-.1049s-.3611.1267-.4862.2717c-.125.145-.1949.3294-.1974.5208s.0627.3776.184.5257c-.0454.0515-.0774.1133-.0934.18-.0407.1143-.0407.2391 0 .3533h.76v-.04c.0133-.0637.0133-.1295 0-.1933.1451-.0305.2811-.0946.3969-.1872.1158-.0927.2082-.2112.2698-.3461.0824-.1698.0987-.3642.0457-.5453-.0529-.1812-.1714-.3361-.3324-.4347z" fill="#01ada1"/><path d="m42.4527 51.3731c.08-.1867-.06-.4-.1667-.5334-.1066-.1333-.3866 0-.4733.16s0 .42.1533.4667c.1534.0467.4067.0867.4867-.0933z" fill="#01ada1"/><path d="m31.3863 50.8071c-.3533-.1333-.7466.0001-.84.2201-.041.1998-.0027.4078.1067.58h1.1267c.0359-.0776.054-.1622.053-.2476-.001-.0855-.0211-.1696-.0588-.2463s-.0921-.144-.1591-.197c-.0671-.0529-.1451-.0902-.2285-.1092z" fill="#01ada1"/><path d="m39.9131 51.6068h.5333c-.0597-.0442-.1277-.076-.2-.0934-.059-.0099-.1195-.0067-.1771.0095-.0576.0161-.1109.0448-.1562.0839z" fill="#01ada1"/><path d="m39.9999 48.827c-.0166-.0695-.0494-.1342-.0958-.1887-.0463-.0544-.1049-.0971-.1709-.1246-.1133 0-.4333 0-.4867.1466-.0533.1467.2.3667.3134.4334.1133.0666.44-.08.44-.2667z" fill="#01ada1"/><path d="m58.5392 39.7h-3.66l-1.22-11.3934h6.1z" fill="#fff"/><path d="m58.946 44.7835c0-1.2352-1.0014-2.2366-2.2367-2.2366s-2.2366 1.0014-2.2366 2.2366c0 1.2353 1.0013 2.2367 2.2366 2.2367s2.2367-1.0014 2.2367-2.2367z" fill="#fff"/></svg> \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/related_alerts.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/related_alerts.tsx index 944481e60fbe5..6ccebc34e4722 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/related_alerts.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/related_alerts.tsx @@ -6,13 +6,34 @@ */ import React, { useState, useRef, useEffect } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiImage, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; -import { ALERT_END, ALERT_START, ALERT_UUID } from '@kbn/rule-data-utils'; +import { + ALERT_END, + ALERT_GROUP, + ALERT_RULE_UUID, + ALERT_START, + ALERT_UUID, + TAGS, +} from '@kbn/rule-data-utils'; import { BoolQuery, Filter, type Query } from '@kbn/es-query'; import { AlertsGrouping } from '@kbn/alerts-grouping'; +import { ObservabilityFields } from '../../../../common/utils/alerting/types'; import { observabilityAlertFeatureIds } from '../../../../common/constants'; +import { + getRelatedAlertKuery, + getSharedFields, +} from '../../../../common/utils/alerting/get_related_alerts_query'; import { TopAlert } from '../../..'; import { AlertSearchBarContainerState, @@ -30,25 +51,25 @@ import { Provider, useAlertSearchBarStateContainer, } from '../../../components/alert_search_bar/containers'; -import { ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID, SEARCH_BAR_URL_STORAGE_KEY } from '../../../constants'; +import { RELATED_ALERTS_TABLE_CONFIG_ID, SEARCH_BAR_URL_STORAGE_KEY } from '../../../constants'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import { useKibana } from '../../../utils/kibana_react'; import { buildEsQuery } from '../../../utils/build_es_query'; import { mergeBoolQueries } from '../../alerts/helpers/merge_bool_queries'; +import icon from './assets/illustration_product_no_results_magnifying_glass.svg'; const ALERTS_PER_PAGE = 50; const RELATED_ALERTS_SEARCH_BAR_ID = 'related-alerts-search-bar-o11y'; const ALERTS_TABLE_ID = 'xpack.observability.related.alerts.table'; interface Props { - alert: TopAlert; - kuery: string; + alert?: TopAlert<ObservabilityFields>; } const defaultState: AlertSearchBarContainerState = { ...DEFAULT_STATE, status: 'active' }; const DEFAULT_FILTERS: Filter[] = []; -export function InternalRelatedAlerts({ alert, kuery }: Props) { +export function InternalRelatedAlerts({ alert }: Props) { const kibanaServices = useKibana().services; const { http, @@ -62,9 +83,14 @@ export function InternalRelatedAlerts({ alert, kuery }: Props) { }); const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>(); - const alertStart = alert.fields[ALERT_START]; - const alertEnd = alert.fields[ALERT_END]; - const alertId = alert.fields[ALERT_UUID]; + const alertStart = alert?.fields[ALERT_START]; + const alertEnd = alert?.fields[ALERT_END]; + const alertId = alert?.fields[ALERT_UUID]; + const tags = alert?.fields[TAGS]; + const groups = alert?.fields[ALERT_GROUP]; + const ruleId = alert?.fields[ALERT_RULE_UUID]; + const sharedFields = getSharedFields(alert?.fields); + const kuery = getRelatedAlertKuery({ tags, groups, ruleId, sharedFields }); const defaultQuery = useRef<Query[]>([ { query: `not kibana.alert.uuid: ${alertId}`, language: 'kuery' }, @@ -79,6 +105,8 @@ export function InternalRelatedAlerts({ alert, kuery }: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [alertStart, alertEnd]); + if (!kuery || !alert) return <EmptyState />; + return ( <EuiFlexGroup direction="column" gutterSize="m"> <EuiFlexItem> @@ -103,7 +131,7 @@ export function InternalRelatedAlerts({ alert, kuery }: Props) { to={alertSearchBarStateProps.rangeTo} globalFilters={alertSearchBarStateProps.filters ?? DEFAULT_FILTERS} globalQuery={{ query: alertSearchBarStateProps.kuery, language: 'kuery' }} - groupingId={ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID} + groupingId={RELATED_ALERTS_TABLE_CONFIG_ID} defaultGroupingOptions={DEFAULT_GROUPING_OPTIONS} getAggregationsByGroupingField={getAggregationsByGroupingField} renderGroupPanel={renderGroupPanel} @@ -122,7 +150,7 @@ export function InternalRelatedAlerts({ alert, kuery }: Props) { <AlertsStateTable id={ALERTS_TABLE_ID} featureIds={observabilityAlertFeatureIds} - configurationId={ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID} + configurationId={RELATED_ALERTS_TABLE_CONFIG_ID} query={mergeBoolQueries(esQuery, groupQuery)} showAlertStatusWithFlapping initialPageSize={ALERTS_PER_PAGE} @@ -138,6 +166,50 @@ export function InternalRelatedAlerts({ alert, kuery }: Props) { ); } +const heights = { + tall: 490, + short: 250, +}; +const panelStyle = { + maxWidth: 500, +}; + +function EmptyState() { + return ( + <EuiPanel color="subdued" data-test-subj="relatedAlertsTabEmptyState"> + <EuiFlexGroup style={{ height: heights.tall }} alignItems="center" justifyContent="center"> + <EuiFlexItem grow={false}> + <EuiPanel hasBorder={true} style={panelStyle}> + <EuiFlexGroup> + <EuiFlexItem> + <EuiText size="s"> + <EuiTitle> + <h3> + <FormattedMessage + id="xpack.observability.pages.alertDetails.relatedAlerts.empty.title" + defaultMessage="Problem loading related alerts" + /> + </h3> + </EuiTitle> + <p> + <FormattedMessage + id="xpack.observability.pages.alertDetails.relatedAlerts.empty.description" + defaultMessage="Due to an unexpected error, no related alerts can be found." + /> + </p> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiImage style={{ width: 200, height: 148 }} size="200" alt="" url={icon} /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPanel> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPanel> + ); +} + export function RelatedAlerts(props: Props) { return ( <Provider value={alertSearchBarStateContainer}> From da63469091f9b76e9e5d053cf11a4df15e14fdef Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" <christiane.heiligers@elastic.co> Date: Tue, 15 Oct 2024 14:14:56 -0700 Subject: [PATCH 65/84] Update cli-description (#196208) Updates the `cli` description text to reflect license change from https://github.com/elastic/kibana/pull/192025. --- src/cli/cli.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli/cli.js b/src/cli/cli.js index f87cc6b5c443e..7ca1f5a694615 100644 --- a/src/cli/cli.js +++ b/src/cli/cli.js @@ -19,8 +19,7 @@ const program = new Command('bin/kibana'); program .version(pkg.version) .description( - 'Kibana is an open and free, browser ' + - 'based analytics and search dashboard for Elasticsearch.' + 'Kibana is an open source, browser based analytics and search dashboard for Elasticsearch.' ); // attach commands From b67bd83ea93909d809206b1004c306a11fd8ee3f Mon Sep 17 00:00:00 2001 From: Ryland Herrick <ryalnd@gmail.com> Date: Tue, 15 Oct 2024 16:26:25 -0500 Subject: [PATCH 66/84] [Security Solution] Allow exporting of prebuilt rules via the API (#194498) ## Summary This PR introduces the backend functionality necessary to export prebuilt rules via our existing export APIs: 1. Export Rules - POST /rules/_export 2. Bulk Actions - POST /rules/_bulk_action The [Prebuilt Rule Customization RFC](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/docs/rfcs/detection_response/prebuilt_rules_customization.md) goes into detail, and the export-specific issue is described [here](https://github.com/elastic/kibana/issues/180167#issue-2227974379). ## Steps to Review 1. Enable the Feature Flag: `prebuiltRulesCustomizationEnabled` 1. Install the prebuilt rules package via fleet 1. Install some prebuilt rules, and obtain a prebuilt rule's `rule_id`, e.g. `ac8805f6-1e08-406c-962e-3937057fa86f` 1. Export the rule via the export route, e.g. (in Dev Tools): POST kbn:api/detection_engine/rules/_export Note that you may need to use the CURL equivalent for these requests, as the dev console does not seem to handle file responses: curl --location --request POST 'http://localhost:5601/api/detection_engine/rules/_export?exclude_export_details=true&file_name=exported_rules.ndjson' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 2023-10-31' \ --header 'Authorization: Basic waefoijawoefiajweo==' 1. Export the rule via bulk actions, e.g. (in Dev Tools): POST kbn:api/detection_engine/rules/_bulk_action { "action": "export" } 1. Observe that the exported rules' fields are correct, especially `rule_source` and `immutable` (see tests added here for examples). ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed ### 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) --- .../api/rules/bulk_actions/route.ts | 3 +- .../api/rules/export_rules/route.ts | 30 +- .../logic/export/get_export_all.ts | 10 +- .../logic/export/get_export_by_object_ids.ts | 15 +- .../trial_license_complete_tier/index.ts | 1 + .../rules_export.ts | 335 ++++++++++++++++++ 6 files changed, 379 insertions(+), 15 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/rules_export.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 4d31bd457a3e9..658a9b193e0a2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -281,7 +281,8 @@ export const performBulkActionRoute = ( rules.map(({ params }) => params.ruleId), exporter, request, - actionsClient + actionsClient, + config.experimentalFeatures.prebuiltRulesCustomizationEnabled ); const responseBody = `${exported.rulesNdjson}${exported.exceptionLists}${exported.actionConnectors}${exported.exportDetails}`; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts index 478a0ce02cc96..3c770c714334c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts @@ -15,7 +15,10 @@ import { } from '../../../../../../../common/api/detection_engine/rule_management'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import type { ConfigType } from '../../../../../../config'; -import { getNonPackagedRulesCount } from '../../../logic/search/get_existing_prepackaged_rules'; +import { + getNonPackagedRulesCount, + getRulesCount, +} from '../../../logic/search/get_existing_prepackaged_rules'; import { getExportByObjectIds } from '../../../logic/export/get_export_by_object_ids'; import { getExportAll } from '../../../logic/export/get_export_all'; import { buildSiemResponse } from '../../../../routes/utils'; @@ -57,6 +60,8 @@ export const exportRulesRoute = ( const client = getClient({ includedHiddenTypes: ['action'] }); const actionsExporter = getExporter(client); + const { prebuiltRulesCustomizationEnabled } = config.experimentalFeatures; + try { const exportSizeLimit = config.maxRuleImportExportSize; if (request.body?.objects != null && request.body.objects.length > exportSizeLimit) { @@ -65,10 +70,19 @@ export const exportRulesRoute = ( body: `Can't export more than ${exportSizeLimit} rules`, }); } else { - const nonPackagedRulesCount = await getNonPackagedRulesCount({ - rulesClient, - }); - if (nonPackagedRulesCount > exportSizeLimit) { + let rulesCount = 0; + + if (prebuiltRulesCustomizationEnabled) { + rulesCount = await getRulesCount({ + rulesClient, + filter: '', + }); + } else { + rulesCount = await getNonPackagedRulesCount({ + rulesClient, + }); + } + if (rulesCount > exportSizeLimit) { return siemResponse.error({ statusCode: 400, body: `Can't export more than ${exportSizeLimit} rules`, @@ -84,14 +98,16 @@ export const exportRulesRoute = ( request.body.objects.map((obj) => obj.rule_id), actionsExporter, request, - actionsClient + actionsClient, + prebuiltRulesCustomizationEnabled ) : await getExportAll( rulesClient, exceptionsClient, actionsExporter, request, - actionsClient + actionsClient, + prebuiltRulesCustomizationEnabled ); const responseBody = request.query.exclude_export_details diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts index cdf8c6333e595..4407a15622cd6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts @@ -11,7 +11,7 @@ import type { ISavedObjectsExporter, KibanaRequest } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { ActionsClient } from '@kbn/actions-plugin/server'; -import { getNonPackagedRules } from '../search/get_existing_prepackaged_rules'; +import { getNonPackagedRules, getRules } from '../search/get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { transformAlertsToRules } from '../../utils/utils'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; @@ -23,14 +23,18 @@ export const getExportAll = async ( exceptionsClient: ExceptionListClient | undefined, actionsExporter: ISavedObjectsExporter, request: KibanaRequest, - actionsClient: ActionsClient + actionsClient: ActionsClient, + prebuiltRulesCustomizationEnabled?: boolean ): Promise<{ rulesNdjson: string; exportDetails: string; exceptionLists: string | null; actionConnectors: string; + prebuiltRulesCustomizationEnabled?: boolean; }> => { - const ruleAlertTypes = await getNonPackagedRules({ rulesClient }); + const ruleAlertTypes = prebuiltRulesCustomizationEnabled + ? await getRules({ rulesClient, filter: '' }) + : await getNonPackagedRules({ rulesClient }); const rules = transformAlertsToRules(ruleAlertTypes); const exportRules = rules.map((r) => transformRuleToExportableFormat(r)); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts index 7c3142aed85f6..02355d39e7e6d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts @@ -29,15 +29,21 @@ export const getExportByObjectIds = async ( ruleIds: string[], actionsExporter: ISavedObjectsExporter, request: KibanaRequest, - actionsClient: ActionsClient + actionsClient: ActionsClient, + prebuiltRulesCustomizationEnabled?: boolean ): Promise<{ rulesNdjson: string; exportDetails: string; exceptionLists: string | null; actionConnectors: string; + prebuiltRulesCustomizationEnabled?: boolean; }> => withSecuritySpan('getExportByObjectIds', async () => { - const rulesAndErrors = await fetchRulesByIds(rulesClient, ruleIds); + const rulesAndErrors = await fetchRulesByIds( + rulesClient, + ruleIds, + prebuiltRulesCustomizationEnabled + ); const { rules, missingRuleIds } = rulesAndErrors; // Retrieve exceptions @@ -76,7 +82,8 @@ interface FetchRulesResult { const fetchRulesByIds = async ( rulesClient: RulesClient, - ruleIds: string[] + ruleIds: string[], + prebuiltRulesCustomizationEnabled?: boolean ): Promise<FetchRulesResult> => { // It's important to avoid too many clauses in the request otherwise ES will fail to process the request // with `too_many_clauses` error (see https://github.com/elastic/kibana/issues/170015). The clauses limit @@ -110,7 +117,7 @@ const fetchRulesByIds = async ( return matchingRule != null && hasValidRuleType(matchingRule) && - matchingRule.params.immutable !== true + (prebuiltRulesCustomizationEnabled || matchingRule.params.immutable !== true) ? { rule: transformRuleToExportableFormat(internalRuleToAPIResponse(matchingRule)), } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts index 76a461d438463..4324ce4602d72 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts @@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { describe('Rules Management - Prebuilt Rules - Update Prebuilt Rules Package', function () { loadTestFile(require.resolve('./is_customized_calculation')); + loadTestFile(require.resolve('./rules_export')); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/rules_export.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/rules_export.ts new file mode 100644 index 0000000000000..e49a23f6138a3 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/rules_export.ts @@ -0,0 +1,335 @@ +/* + * 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 { + BulkActionEditTypeEnum, + BulkActionTypeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { + binaryToString, + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + deleteAllPrebuiltRuleAssets, + getCustomQueryRuleParams, + installPrebuiltRules, +} from '../../../../utils'; + +const parseNdJson = (ndJson: Buffer): unknown[] => + ndJson + .toString() + .split('\n') + .filter((line) => !!line) + .map((line) => JSON.parse(line)); + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + const log = getService('log'); + + /** + * This test suite is skipped in Serverless MKI environments due to reliance on the + * feature flag for prebuilt rule customization. + */ + describe('@ess @serverless @skipInServerlessMKI Exporting Rules with Prebuilt Rule Customization', () => { + beforeEach(async () => { + await deleteAllPrebuiltRuleAssets(es, log); + await deleteAllRules(supertest, log); + }); + + it('exports a set of custom installed rules via the _export API', async () => { + await securitySolutionApi + .bulkCreateRules({ + body: [ + getCustomQueryRuleParams({ rule_id: 'rule-id-1' }), + getCustomQueryRuleParams({ rule_id: 'rule-id-2' }), + ], + }) + .expect(200); + + const { body: exportResult } = await securitySolutionApi + .exportRules({ query: {}, body: null }) + .expect(200) + .parse(binaryToString); + + const ndJson = parseNdJson(exportResult); + + expect(ndJson).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: 'rule-id-1', + rule_source: { + type: 'internal', + }, + }), + expect.objectContaining({ + rule_id: 'rule-id-2', + rule_source: { + type: 'internal', + }, + }), + ]) + ); + }); + + describe('with prebuilt rules installed', () => { + let ruleAssets: Array<ReturnType<typeof createRuleAssetSavedObject>>; + + beforeEach(async () => { + ruleAssets = [ + createRuleAssetSavedObject({ + rule_id: '000047bb-b27a-47ec-8b62-ef1a5d2c9e19', + tags: ['test-tag'], + }), + createRuleAssetSavedObject({ + rule_id: '60b88c41-c45d-454d-945c-5809734dfb34', + tags: ['test-tag-2'], + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, ruleAssets); + await installPrebuiltRules(es, supertest); + }); + + it('exports a set of prebuilt installed rules via the _export API', async () => { + const { body: exportResult } = await securitySolutionApi + .exportRules({ query: {}, body: null }) + .expect(200) + .parse(binaryToString); + + const parsedExportResult = parseNdJson(exportResult); + + expect(parsedExportResult).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: ruleAssets[0]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + expect.objectContaining({ + rule_id: ruleAssets[1]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + ]) + ); + + const [firstExportedRule, secondExportedRule] = parsedExportResult as Array<{ + id: string; + rule_id: string; + }>; + + const { body: bulkEditResult } = await securitySolutionApi + .performRulesBulkAction({ + query: {}, + body: { + ids: [firstExportedRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.add_tags, + value: ['new-tag'], + }, + ], + }, + }) + .expect(200); + + expect(bulkEditResult.attributes.summary).toEqual({ + failed: 0, + skipped: 0, + succeeded: 1, + total: 1, + }); + expect(bulkEditResult.attributes.results.updated[0].rule_source.is_customized).toEqual( + true + ); + + const { body: secondExportResult } = await securitySolutionApi + .exportRules({ query: {}, body: null }) + .expect(200) + .parse(binaryToString); + + expect(parseNdJson(secondExportResult)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: firstExportedRule.rule_id, + rule_source: { + type: 'external', + is_customized: true, + }, + }), + expect.objectContaining({ + rule_id: secondExportedRule.rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + ]) + ); + }); + + it('exports a set of custom and prebuilt installed rules via the _export API', async () => { + await securitySolutionApi + .bulkCreateRules({ + body: [ + getCustomQueryRuleParams({ rule_id: 'rule-id-1' }), + getCustomQueryRuleParams({ rule_id: 'rule-id-2' }), + ], + }) + .expect(200); + + const { body: exportResult } = await securitySolutionApi + .exportRules({ query: {}, body: null }) + .expect(200) + .parse(binaryToString); + + const exportJson = parseNdJson(exportResult); + expect(exportJson).toHaveLength(5); // 2 prebuilt rules + 2 custom rules + 1 stats object + + expect(exportJson).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: ruleAssets[0]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + expect.objectContaining({ + rule_id: ruleAssets[1]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + expect.objectContaining({ + rule_id: 'rule-id-1', + rule_source: { + type: 'internal', + }, + }), + expect.objectContaining({ + rule_id: 'rule-id-2', + rule_source: { + type: 'internal', + }, + }), + ]) + ); + }); + + it('exports both custom and prebuilt rules when rule_ids are specified via the _export API', async () => { + await securitySolutionApi + .bulkCreateRules({ + body: [ + getCustomQueryRuleParams({ rule_id: 'rule-id-1' }), + getCustomQueryRuleParams({ rule_id: 'rule-id-2' }), + ], + }) + .expect(200); + + const { body: exportResult } = await securitySolutionApi + .exportRules({ + query: {}, + body: { + objects: [ + { rule_id: ruleAssets[1]['security-rule'].rule_id }, + { rule_id: 'rule-id-2' }, + ], + }, + }) + .expect(200) + .parse(binaryToString); + + const exportJson = parseNdJson(exportResult); + expect(exportJson).toHaveLength(3); // 1 prebuilt rule + 1 custom rule + 1 stats object + + expect(exportJson).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: ruleAssets[1]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + expect.objectContaining({ + rule_id: 'rule-id-2', + rule_source: { + type: 'internal', + }, + }), + ]) + ); + }); + + it('exports a set of custom and prebuilt installed rules via the bulk_actions API', async () => { + await securitySolutionApi + .bulkCreateRules({ + body: [ + getCustomQueryRuleParams({ rule_id: 'rule-id-1' }), + getCustomQueryRuleParams({ rule_id: 'rule-id-2' }), + ], + }) + .expect(200); + + const { body: exportResult } = await securitySolutionApi + .performRulesBulkAction({ + body: { query: '', action: BulkActionTypeEnum.export }, + query: {}, + }) + .expect(200) + .expect('Content-Type', 'application/ndjson') + .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') + .parse(binaryToString); + + const exportJson = parseNdJson(exportResult); + expect(exportJson).toHaveLength(5); // 2 prebuilt rules + 2 custom rules + 1 stats object + + expect(exportJson).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: ruleAssets[0]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + expect.objectContaining({ + rule_id: ruleAssets[1]['security-rule'].rule_id, + rule_source: { + type: 'external', + is_customized: false, + }, + }), + expect.objectContaining({ + rule_id: 'rule-id-1', + rule_source: { + type: 'internal', + }, + }), + expect.objectContaining({ + rule_id: 'rule-id-2', + rule_source: { + type: 'internal', + }, + }), + ]) + ); + }); + }); + }); +}; From e4762201fdd84f372c78bc2a159061e504b26e78 Mon Sep 17 00:00:00 2001 From: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Date: Tue, 15 Oct 2024 23:53:00 +0200 Subject: [PATCH 67/84] [Lens] Fix switchVisualizationType to use it without layerId (#196295) With [PR #187475](https://github.com/elastic/kibana/pull/187475/files) we introduced a bug, affecting the AI assistant's suggestions API when switching between different chart types (e.g., from bar to line chart). This feature relies on the switchVisualizationType method, which was changed to require a `layerId`. However, the suggestions API does not provide `layerId`, causing the chart type to not switch as expected. Solution: The bug can be resolved by modifying the logic in the `switchVisualizationType` method. We changed: ```ts const dataLayer = state.layers.find((l) => l.layerId === layerId); ``` to: ```ts const dataLayer = state.layers.find((l) => l.layerId === layerId) ?? state.layers[0]; ``` This ensures that the suggestions API falls back to the first layer if no specific layerId is provided. --------- Co-authored-by: Marco Vettorello <vettorello.marco@gmail.com> --- .../visualizations/xy/visualization.test.tsx | 26 +++++++++++++++++++ .../visualizations/xy/visualization.tsx | 5 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx index 012823831b8eb..e2c6ce25bd2e3 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx @@ -4214,4 +4214,30 @@ describe('xy_visualization', () => { `); }); }); + describe('switchVisualizationType', () => { + it('should switch all the layers to the new visualization type if layerId is not specified (AI assistant case)', () => { + const state = exampleState(); + state.layers[1] = state.layers[0]; + state.layers[1].layerId = 'second'; + state.layers[2] = state.layers[0]; + state.layers[2].layerId = 'third'; + const newType = 'bar'; + const newState = xyVisualization.switchVisualizationType!(newType, state); + expect((newState.layers[0] as XYDataLayerConfig).seriesType).toEqual(newType); + expect((newState.layers[1] as XYDataLayerConfig).seriesType).toEqual(newType); + expect((newState.layers[2] as XYDataLayerConfig).seriesType).toEqual(newType); + }); + it('should switch only the second layer to the new visualization type if layerId is specified (chart switch case)', () => { + const state = exampleState(); + state.layers[1] = { ...state.layers[0] }; + state.layers[1].layerId = 'second'; + state.layers[2] = { ...state.layers[0] }; + state.layers[2].layerId = 'third'; + const newType = 'bar'; + const newState = xyVisualization.switchVisualizationType!(newType, state, 'first'); + expect((newState.layers[0] as XYDataLayerConfig).seriesType).toEqual(newType); + expect((newState.layers[1] as XYDataLayerConfig).seriesType).toEqual('area'); + expect((newState.layers[2] as XYDataLayerConfig).seriesType).toEqual('area'); + }); + }); }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 64a2ad4fc2754..6f17a2253a35e 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -263,14 +263,15 @@ export const getXyVisualization = ({ getDescription, switchVisualizationType(seriesType: string, state: State, layerId?: string) { - const dataLayer = state.layers.find((l) => l.layerId === layerId); + const dataLayer = layerId + ? state.layers.find((l) => l.layerId === layerId) + : state.layers.at(0); if (dataLayer && !isDataLayer(dataLayer)) { throw new Error('Cannot switch series type for non-data layer'); } if (!dataLayer) { return state; } - // todo: test how they switch between percentage etc const currentStackingType = stackingTypes.find(({ subtypes }) => subtypes.includes(dataLayer.seriesType) ); From cb309882166172fa59aac0d0e839edcd1ae43c61 Mon Sep 17 00:00:00 2001 From: Kylie Meli <kylie.geller@elastic.co> Date: Tue, 15 Oct 2024 18:03:42 -0400 Subject: [PATCH 68/84] [Automatic Import] Fixing only show cel generation flow when user selects cel input (#196356) ## Summary Fixing the Automatic Import flow so that cel generation only occurs when user selects the CEL input in the dropdown. --- .../create_integration_assistant.test.tsx | 22 +++++++- .../create_integration_assistant.tsx | 8 ++- .../footer/footer.test.tsx | 56 ++++++++++++------- .../footer/footer.tsx | 11 ++-- .../mocks/state.ts | 2 + .../create_integration_assistant/state.ts | 6 ++ .../data_stream_step/data_stream_step.tsx | 9 ++- 7 files changed, 83 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.test.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.test.tsx index b6fe577865822..ca4d50958005d 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.test.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.test.tsx @@ -17,6 +17,7 @@ export const defaultInitialState: State = { connector: undefined, integrationSettings: undefined, isGenerating: false, + hasCelInput: false, result: undefined, }; const mockInitialState = jest.fn((): State => defaultInitialState); @@ -168,9 +169,9 @@ describe('CreateIntegration with generateCel enabled', () => { } as never); }); - describe('when step is 5', () => { + describe('when step is 5 and has celInput', () => { beforeEach(() => { - mockInitialState.mockReturnValueOnce({ ...defaultInitialState, step: 5 }); + mockInitialState.mockReturnValueOnce({ ...defaultInitialState, step: 5, hasCelInput: true }); }); it('should render cel input', () => { @@ -184,9 +185,24 @@ describe('CreateIntegration with generateCel enabled', () => { }); }); + describe('when step is 5 and does not have celInput', () => { + beforeEach(() => { + mockInitialState.mockReturnValueOnce({ ...defaultInitialState, step: 5 }); + }); + + it('should render deploy', () => { + const result = renderIntegrationAssistant(); + expect(result.queryByTestId('deployStepMock')).toBeInTheDocument(); + }); + }); + describe('when step is 6', () => { beforeEach(() => { - mockInitialState.mockReturnValueOnce({ ...defaultInitialState, step: 6 }); + mockInitialState.mockReturnValueOnce({ + ...defaultInitialState, + step: 6, + celInputResult: { program: 'program', stateSettings: {}, redactVars: [] }, + }); }); it('should render review', () => { diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.tsx index 1297e7c975e3b..72e085e19920a 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/create_integration_assistant.tsx @@ -44,6 +44,9 @@ export const CreateIntegrationAssistant = React.memo(() => { setIsGenerating: (payload) => { dispatch({ type: 'SET_IS_GENERATING', payload }); }, + setHasCelInput: (payload) => { + dispatch({ type: 'SET_HAS_CEL_INPUT', payload }); + }, setResult: (payload) => { dispatch({ type: 'SET_GENERATED_RESULT', payload }); }, @@ -93,7 +96,7 @@ export const CreateIntegrationAssistant = React.memo(() => { /> )} {state.step === 5 && - (isGenerateCelEnabled ? ( + (isGenerateCelEnabled && state.hasCelInput ? ( <CelInputStep integrationSettings={state.integrationSettings} connector={state.connector} @@ -107,7 +110,7 @@ export const CreateIntegrationAssistant = React.memo(() => { /> ))} - {isGenerateCelEnabled && state.step === 6 && ( + {isGenerateCelEnabled && state.celInputResult && state.step === 6 && ( <ReviewCelStep isGenerating={state.isGenerating} celInputResult={state.celInputResult} @@ -125,6 +128,7 @@ export const CreateIntegrationAssistant = React.memo(() => { <Footer currentStep={state.step} isGenerating={state.isGenerating} + hasCelInput={state.hasCelInput} isNextStepEnabled={isNextStepEnabled} /> </KibanaPageTemplate> diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.test.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.test.tsx index 900a72ab272a0..1ca79210bb19f 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.test.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.test.tsx @@ -42,9 +42,12 @@ describe('Footer', () => { describe('when rendered', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={1} isGenerating={false} isNextStepEnabled />, { - wrapper, - }); + result = render( + <Footer currentStep={1} isGenerating={false} hasCelInput={false} isNextStepEnabled />, + { + wrapper, + } + ); }); it('should render footer buttons component', () => { expect(result.queryByTestId('buttonsFooter')).toBeInTheDocument(); @@ -66,9 +69,12 @@ describe('Footer', () => { describe('when step is 1', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={1} isGenerating={false} isNextStepEnabled />, { - wrapper, - }); + result = render( + <Footer currentStep={1} isGenerating={false} hasCelInput={false} isNextStepEnabled />, + { + wrapper, + } + ); }); describe('when next button is clicked', () => { @@ -112,9 +118,12 @@ describe('Footer', () => { describe('when step is 2', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={2} isGenerating={false} isNextStepEnabled />, { - wrapper, - }); + result = render( + <Footer currentStep={2} isGenerating={false} hasCelInput={false} isNextStepEnabled />, + { + wrapper, + } + ); }); describe('when next button is clicked', () => { @@ -159,9 +168,12 @@ describe('Footer', () => { describe('when it is not generating', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={3} isGenerating={false} isNextStepEnabled />, { - wrapper, - }); + result = render( + <Footer currentStep={3} isGenerating={false} hasCelInput={false} isNextStepEnabled />, + { + wrapper, + } + ); }); describe('when next button is clicked', () => { @@ -205,9 +217,12 @@ describe('Footer', () => { describe('when it is generating', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={3} isGenerating={true} isNextStepEnabled />, { - wrapper, - }); + result = render( + <Footer currentStep={3} isGenerating={true} hasCelInput={false} isNextStepEnabled />, + { + wrapper, + } + ); }); it('should render the loader', () => { @@ -219,9 +234,12 @@ describe('Footer', () => { describe('when step is 4', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={4} isGenerating={false} isNextStepEnabled />, { - wrapper, - }); + result = render( + <Footer currentStep={4} isGenerating={false} hasCelInput={false} isNextStepEnabled />, + { + wrapper, + } + ); }); describe('when next button is clicked', () => { @@ -265,7 +283,7 @@ describe('Footer', () => { describe('when next step is disabled', () => { let result: RenderResult; beforeEach(() => { - result = render(<Footer currentStep={1} isGenerating={false} />, { + result = render(<Footer currentStep={1} isGenerating={false} hasCelInput={false} />, { wrapper, }); }); diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.tsx index 9a2f862264e27..839d751e6f380 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/footer/footer.tsx @@ -45,11 +45,12 @@ AnalyzeCelButtonText.displayName = 'AnalyzeCelButtonText'; interface FooterProps { currentStep: State['step']; isGenerating: State['isGenerating']; + hasCelInput: State['hasCelInput']; isNextStepEnabled?: boolean; } export const Footer = React.memo<FooterProps>( - ({ currentStep, isGenerating, isNextStepEnabled = false }) => { + ({ currentStep, isGenerating, hasCelInput, isNextStepEnabled = false }) => { const telemetry = useTelemetry(); const { setStep, setIsGenerating } = useActions(); const navigate = useNavigate(); @@ -77,18 +78,18 @@ export const Footer = React.memo<FooterProps>( if (currentStep === 3) { return <AnalyzeButtonText isGenerating={isGenerating} />; } - if (currentStep === 4 && !isGenerateCelEnabled) { + if (currentStep === 4 && (!isGenerateCelEnabled || !hasCelInput)) { return i18n.ADD_TO_ELASTIC; } - if (currentStep === 5 && isGenerateCelEnabled) { + if (currentStep === 5 && isGenerateCelEnabled && hasCelInput) { return <AnalyzeCelButtonText isGenerating={isGenerating} />; } if (currentStep === 6 && isGenerateCelEnabled) { return i18n.ADD_TO_ELASTIC; } - }, [currentStep, isGenerating, isGenerateCelEnabled]); + }, [currentStep, isGenerating, hasCelInput, isGenerateCelEnabled]); - if (currentStep === 7 || (currentStep === 5 && !isGenerateCelEnabled)) { + if (currentStep === 7 || (currentStep === 5 && (!isGenerateCelEnabled || !hasCelInput))) { return <ButtonsFooter cancelButtonText={i18n.CLOSE} />; } return ( diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/mocks/state.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/mocks/state.ts index 452d5e65a972c..c25a78a35416e 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/mocks/state.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/mocks/state.ts @@ -423,6 +423,7 @@ export const mockState: State = { logSamples: rawSamples, }, isGenerating: false, + hasCelInput: false, result, }; @@ -431,6 +432,7 @@ export const mockActions: Actions = { setConnector: jest.fn(), setIntegrationSettings: jest.fn(), setIsGenerating: jest.fn(), + setHasCelInput: jest.fn(), setResult: jest.fn(), setCelInputResult: jest.fn(), }; diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/state.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/state.ts index 0492012ab8686..1e7b22128843b 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/state.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/state.ts @@ -13,6 +13,7 @@ export interface State { connector?: AIConnector; integrationSettings?: IntegrationSettings; isGenerating: boolean; + hasCelInput: boolean; result?: { pipeline: Pipeline; docs: Docs; @@ -26,6 +27,7 @@ export const initialState: State = { connector: undefined, integrationSettings: undefined, isGenerating: false, + hasCelInput: false, result: undefined, }; @@ -34,6 +36,7 @@ type Action = | { type: 'SET_CONNECTOR'; payload: State['connector'] } | { type: 'SET_INTEGRATION_SETTINGS'; payload: State['integrationSettings'] } | { type: 'SET_IS_GENERATING'; payload: State['isGenerating'] } + | { type: 'SET_HAS_CEL_INPUT'; payload: State['hasCelInput'] } | { type: 'SET_GENERATED_RESULT'; payload: State['result'] } | { type: 'SET_CEL_INPUT_RESULT'; payload: State['celInputResult'] }; @@ -52,6 +55,8 @@ export const reducer = (state: State, action: Action): State => { return { ...state, integrationSettings: action.payload }; case 'SET_IS_GENERATING': return { ...state, isGenerating: action.payload }; + case 'SET_HAS_CEL_INPUT': + return { ...state, hasCelInput: action.payload }; case 'SET_GENERATED_RESULT': return { ...state, @@ -70,6 +75,7 @@ export interface Actions { setConnector: (payload: State['connector']) => void; setIntegrationSettings: (payload: State['integrationSettings']) => void; setIsGenerating: (payload: State['isGenerating']) => void; + setHasCelInput: (payload: State['hasCelInput']) => void; setResult: (payload: State['result']) => void; setCelInputResult: (payload: State['celInputResult']) => void; } diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/data_stream_step.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/data_stream_step.tsx index 4e93d17adedd5..4b505fb7062d6 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/data_stream_step.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/data_stream_step.tsx @@ -53,7 +53,8 @@ interface DataStreamStepProps { } export const DataStreamStep = React.memo<DataStreamStepProps>( ({ integrationSettings, connector, isGenerating }) => { - const { setIntegrationSettings, setIsGenerating, setStep, setResult } = useActions(); + const { setIntegrationSettings, setIsGenerating, setHasCelInput, setStep, setResult } = + useActions(); const { isLoading: isLoadingPackageNames, packageNames } = useLoadPackageNames(); // this is used to avoid duplicate names const [name, setName] = useState<string>(integrationSettings?.name ?? ''); @@ -99,9 +100,13 @@ export const DataStreamStep = React.memo<DataStreamStepProps>( setIntegrationValues({ dataStreamDescription: e.target.value }), inputTypes: (options: EuiComboBoxOptionOption[]) => { setIntegrationValues({ inputTypes: options.map((option) => option.value as InputType) }); + setHasCelInput( + // the cel value here comes from the input type options defined above + options.map((option) => option.value as InputType).includes('cel' as InputType) + ); }, }; - }, [setIntegrationValues, setInvalidFields, packageNames]); + }, [setIntegrationValues, setInvalidFields, setHasCelInput, packageNames]); useEffect(() => { // Pre-populates the name from the title set in the previous step. From d89f32a6aca0b522c606e5aec668cee5a3267d4a Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:04:04 -0500 Subject: [PATCH 69/84] [ML] Add control to show or hide empty fields in dropdown in Transform (#195485) ## Summary Follow up of https://github.com/elastic/kibana/pull/186670. This PR adds a new control show or hide empty fields in dropdowns in Transform. #### Transform Pivot transform creation https://github.com/user-attachments/assets/35366671-c7a0-4ba1-ae24-ae3d965a2d69 Latest transform creation <img width="1473" alt="image" src="https://github.com/user-attachments/assets/db53e7ed-17d5-44d7-93ab-1d0c5ca22f20"> ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../option_list_popover.tsx | 4 +- .../option_list_with_stats.tsx | 18 ++++-- .../options_list_with_stats/types.ts | 3 +- .../aggregation_dropdown/dropdown.tsx | 5 +- .../aggregation_list/sub_aggs_section.tsx | 41 ++++++++++++- .../pivot_configuration.tsx | 59 +++++++++++-------- .../step_define/latest_function_form.tsx | 19 +++--- .../functional/services/transform/wizard.ts | 15 ++++- 8 files changed, 119 insertions(+), 45 deletions(-) diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx index 77b5f8a0d8b15..40b47acad3338 100644 --- a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx +++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx @@ -107,7 +107,9 @@ export const OptionsListPopover = ({ }: OptionsListPopoverProps) => { const { populatedFields } = useFieldStatsFlyoutContext(); - const [showEmptyFields, setShowEmptyFields] = useState(false); + const [showEmptyFields, setShowEmptyFields] = useState( + populatedFields ? !(populatedFields.size > 0) : true + ); const id = useMemo(() => htmlIdGenerator()(), []); const filteredOptions = useMemo(() => { diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx index 244b2d6a511a9..4038047450d5a 100644 --- a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx +++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx @@ -7,7 +7,11 @@ import type { FC } from 'react'; import React, { useMemo, useState } from 'react'; -import type { EuiComboBoxOptionOption, EuiComboBoxSingleSelectionShape } from '@elastic/eui'; +import type { + EuiComboBoxOptionOption, + EuiComboBoxSingleSelectionShape, + EuiFormControlLayoutProps, +} from '@elastic/eui'; import { EuiInputPopover, htmlIdGenerator, EuiFormControlLayout, EuiFieldText } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; @@ -18,8 +22,6 @@ import type { DropDownLabel } from './types'; const MIN_POPOVER_WIDTH = 400; export const optionCss = css` - display: flex; - align-items: center; .euiComboBoxOption__enterBadge { display: none; } @@ -31,7 +33,8 @@ export const optionCss = css` } `; -interface OptionListWithFieldStatsProps { +interface OptionListWithFieldStatsProps + extends Pick<EuiFormControlLayoutProps, 'prepend' | 'compressed'> { options: DropDownLabel[]; placeholder?: string; 'aria-label'?: string; @@ -58,6 +61,8 @@ export const OptionListWithFieldStats: FC<OptionListWithFieldStatsProps> = ({ isDisabled, isLoading, isClearable = true, + prepend, + compressed, 'aria-label': ariaLabel, 'data-test-subj': dataTestSubj, }) => { @@ -68,13 +73,12 @@ export const OptionListWithFieldStats: FC<OptionListWithFieldStatsProps> = ({ const comboBoxOptions: DropDownLabel[] = useMemo( () => Array.isArray(options) - ? options.map(({ isEmpty, hideTrigger: hideInspectButton, ...o }) => ({ + ? options.map(({ isEmpty, ...o }) => ({ ...o, css: optionCss, // Change data-is-empty- because EUI is passing all props to dom element // so isEmpty is invalid, but we need this info to render option correctly 'data-is-empty': isEmpty, - 'data-hide-inspect': hideInspectButton, })) : [], [options] @@ -89,6 +93,8 @@ export const OptionListWithFieldStats: FC<OptionListWithFieldStatsProps> = ({ id={popoverId} input={ <EuiFormControlLayout + prepend={prepend} + compressed={compressed} fullWidth={fullWidth} // Adding classname to make functional tests similar to EuiComboBox className={singleSelection ? 'euiComboBox__inputWrap--plainText' : ''} diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts index ef95daa38ea03..419808b804e21 100644 --- a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts +++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts @@ -9,8 +9,9 @@ import type { EuiComboBoxOptionOption, EuiSelectableOption } from '@elastic/eui' import type { Aggregation, Field } from '@kbn/ml-anomaly-utils'; interface BaseOption<T> { - key?: string; label: string | React.ReactNode; + key?: string; + value?: string | number | string[]; isEmpty?: boolean; hideTrigger?: boolean; 'data-is-empty'?: boolean; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx index eaa9faee8de53..1782fa2df3bb8 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { EuiComboBoxOptionsListProps, EuiComboBoxOptionOption } from '@elastic/eui'; -import { EuiComboBox } from '@elastic/eui'; +import { OptionListWithFieldStats } from '@kbn/ml-field-stats-flyout/options_list_with_stats/option_list_with_stats'; interface Props { options: EuiComboBoxOptionOption[]; @@ -30,7 +30,7 @@ export const DropDown: React.FC<Props> = ({ isDisabled, }) => { return ( - <EuiComboBox + <OptionListWithFieldStats fullWidth placeholder={placeholder} singleSelection={{ asPlainText: true }} @@ -40,7 +40,6 @@ export const DropDown: React.FC<Props> = ({ isClearable={false} data-test-subj={testSubj} isDisabled={isDisabled} - renderOption={renderOption} /> ); }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/sub_aggs_section.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/sub_aggs_section.tsx index d1fa84056a0cd..498563bc23a5f 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/sub_aggs_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/sub_aggs_section.tsx @@ -11,11 +11,14 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiSpacer, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { FieldStatsInfoButton, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout'; import { AggListForm } from './list_form'; import { DropDown } from '../aggregation_dropdown'; import type { PivotAggsConfig } from '../../../../common'; import { PivotConfigurationContext } from '../pivot_configuration/pivot_configuration'; import { MAX_NESTING_SUB_AGGS } from '../../../../common/pivot_aggs'; +import type { DropDownOptionWithField } from '../step_define/common/get_pivot_dropdown_options'; +import type { DropDownOption } from '../../../../common/dropdown'; /** * Component for managing sub-aggregation of the provided @@ -54,11 +57,47 @@ export const SubAggsSection: FC<{ item: PivotAggsConfig }> = ({ item }) => { } return nestingLevel <= MAX_NESTING_SUB_AGGS; }, [item]); + const { handleFieldStatsButtonClick, populatedFields } = useFieldStatsTrigger(); + const options = useMemo(() => { + const opts: EuiComboBoxOptionOption[] = []; + state.aggOptions.forEach(({ label, field, options: aggOptions }: DropDownOptionWithField) => { + const isEmpty = populatedFields && field.id ? !populatedFields.has(field.id) : false; + + const aggOption: DropDownOption = { + isGroupLabel: true, + key: field.id, + searchableLabel: label, + // @ts-ignore Purposefully passing label as element instead of string + // for more robust rendering + label: ( + <FieldStatsInfoButton + isEmpty={populatedFields && !populatedFields.has(field.id)} + field={field} + label={label} + onButtonClick={handleFieldStatsButtonClick} + /> + ), + }; + + if (aggOptions.length) { + opts.push(aggOption); + opts.push( + ...aggOptions.map((o) => ({ + ...o, + isEmpty, + isGroupLabel: false, + searchableLabel: o.label, + })) + ); + } + }); + return opts; + }, [handleFieldStatsButtonClick, populatedFields, state.aggOptions]); const dropdown = ( <DropDown changeHandler={addSubAggHandler} - options={state.aggOptions} + options={options} placeholder={i18n.translate('xpack.transform.stepDefineForm.addSubAggregationPlaceholder', { defaultMessage: 'Add a sub-aggregation ...', })} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx index 798837a1a693f..129f4766d9f28 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx @@ -12,7 +12,6 @@ import { EuiFormRow, type EuiComboBoxOptionOption } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useFieldStatsTrigger, FieldStatsInfoButton } from '@kbn/ml-field-stats-flyout'; - import { type DropDownOptionWithField } from '../step_define/common/get_pivot_dropdown_options'; import type { DropDownOption } from '../../../../common'; import { AggListForm } from '../aggregation_list'; @@ -41,28 +40,42 @@ export const PivotConfiguration: FC<StepDefineFormHook['pivotConfig']> = memo( const { aggList, aggOptions, aggOptionsData, groupByList, groupByOptions, groupByOptionsData } = state; - const aggOptionsWithFieldStats: EuiComboBoxOptionOption[] = useMemo( - () => - aggOptions.map(({ label, field, options }: DropDownOptionWithField) => { - const aggOption: DropDownOption = { - isGroupLabelOption: true, - key: field.id, - // @ts-ignore Purposefully passing label as element instead of string - // for more robust rendering - label: ( - <FieldStatsInfoButton - isEmpty={populatedFields && !populatedFields.has(field.id)} - field={field} - label={label} - onButtonClick={handleFieldStatsButtonClick} - /> - ), - options: options ?? [], - }; - return aggOption; - }), - [aggOptions, handleFieldStatsButtonClick, populatedFields] - ); + const aggOptionsWithFieldStats: EuiComboBoxOptionOption[] = useMemo(() => { + const opts: EuiComboBoxOptionOption[] = []; + aggOptions.forEach(({ label, field, options }: DropDownOptionWithField) => { + const isEmpty = populatedFields && field.id ? !populatedFields.has(field.id) : false; + + const aggOption: DropDownOption = { + isGroupLabel: true, + key: field.id, + searchableLabel: label, + // @ts-ignore Purposefully passing label as element instead of string + // for more robust rendering + label: ( + <FieldStatsInfoButton + isEmpty={populatedFields && !populatedFields.has(field.id)} + field={field} + label={label} + onButtonClick={handleFieldStatsButtonClick} + /> + ), + }; + + if (options.length) { + opts.push(aggOption); + opts.push( + ...options.map((o) => ({ + ...o, + isEmpty, + isGroupLabel: false, + searchableLabel: o.label, + })) + ); + } + }); + return opts; + }, [aggOptions, handleFieldStatsButtonClick, populatedFields]); + return ( <PivotConfigurationContext.Provider value={{ actions, state }}> <EuiFormRow diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx index 9ded43a82c71a..7c0439e3761df 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx @@ -10,7 +10,8 @@ import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButtonIcon, EuiCallOut, EuiComboBox, EuiCopy, EuiFormRow } from '@elastic/eui'; -import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout'; +import type { DropDownLabel } from '@kbn/ml-field-stats-flyout'; +import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout'; import type { LatestFunctionService } from './hooks/use_latest_function_config'; interface LatestFunctionFormProps { @@ -73,7 +74,7 @@ export const LatestFunctionForm: FC<LatestFunctionFormProps> = ({ > <> {latestFunctionService.sortFieldOptions.length > 0 && ( - <EuiComboBox + <OptionListWithFieldStats fullWidth placeholder={i18n.translate('xpack.transform.stepDefineForm.sortPlaceholder', { defaultMessage: 'Add a date field ...', @@ -83,15 +84,19 @@ export const LatestFunctionForm: FC<LatestFunctionFormProps> = ({ selectedOptions={ latestFunctionService.config.sort ? [latestFunctionService.config.sort] : [] } - onChange={(selected) => { - latestFunctionService.updateLatestFunctionConfig({ - sort: { value: selected[0].value, label: selected[0].label as string }, - }); + onChange={(selected: DropDownLabel[]) => { + if (typeof selected[0].value === 'string') { + latestFunctionService.updateLatestFunctionConfig({ + sort: { + value: selected[0].value, + label: selected[0].label?.toString(), + }, + }); + } closeFlyout(); }} isClearable={false} data-test-subj="transformWizardSortFieldSelector" - renderOption={renderOption} /> )} {latestFunctionService.sortFieldOptions.length === 0 && ( diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index 19cb6c15a9f56..7d113c30ffeb1 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -444,7 +444,10 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi }, async setSortFieldValue(identificator: string, label: string) { - await comboBox.set('transformWizardSortFieldSelector > comboBoxInput', identificator); + await ml.commonUI.setOptionsListWithFieldStatsValue( + 'transformWizardSortFieldSelector > comboBoxInput', + identificator + ); await this.assertSortFieldInputValue(identificator); }, @@ -507,7 +510,10 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi expectedLabel: string, expectedIntervalLabel?: string ) { - await comboBox.set('transformGroupBySelection > comboBoxInput', identifier); + await ml.commonUI.setOptionsListWithFieldStatsValue( + 'transformGroupBySelection > comboBoxInput', + identifier + ); await this.assertGroupByInputValue([]); await this.assertGroupByEntryExists(index, expectedLabel, expectedIntervalLabel); }, @@ -582,7 +588,10 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi formData?: Record<string, any>, parentSelector = '' ) { - await comboBox.set(this.getAggComboBoxInputSelector(parentSelector), identifier); + await ml.commonUI.setOptionsListWithFieldStatsValue( + this.getAggComboBoxInputSelector(parentSelector), + identifier + ); await this.assertAggregationInputValue([], parentSelector); await this.assertAggregationEntryExists(index, expectedLabel, parentSelector); From 5fbec1febc0050b2faaba7a25cf4aba9bf0cea1f Mon Sep 17 00:00:00 2001 From: mohamedhamed-ahmed <mohamed.ahmed@elastic.co> Date: Tue, 15 Oct 2024 23:07:38 +0100 Subject: [PATCH 70/84] [Dataset Quality] Hide unreachable links (#196302) closes https://github.com/elastic/kibana/issues/196256 https://github.com/user-attachments/assets/5cec1296-a17a-43bb-8530-99e7261a189a --- .../logs_overview_degraded_fields.tsx | 5 ++-- .../components/dataset_quality_link.tsx | 28 +++++++++++-------- .../components/logs_explorer_top_nav_menu.tsx | 14 +++++++--- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_logs_overview/logs_overview_degraded_fields.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_logs_overview/logs_overview_degraded_fields.tsx index 593ea978db153..3a244dcd5eb3c 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_logs_overview/logs_overview_degraded_fields.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_logs_overview/logs_overview_degraded_fields.tsx @@ -270,13 +270,12 @@ const DatasetQualityLink = React.memo( urlService: BrowserUrlService; dataStream: string | undefined; }) => { - if (!dataStream) { - return null; - } const locator = urlService.locators.get<DataQualityDetailsLocatorParams>( DATA_QUALITY_DETAILS_LOCATOR_ID ); + if (!locator || !dataStream) return null; + const datasetQualityUrl = locator?.getRedirectUrl({ dataStream }); const navigateToDatasetQuality = () => { diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx index 6610db470014b..05c74a9a1c82a 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx @@ -8,7 +8,7 @@ import { EuiHeaderLink } from '@elastic/eui'; import { LogsExplorerPublicState } from '@kbn/logs-explorer-plugin/public'; import { getRouterLinkProps } from '@kbn/router-utils'; -import { BrowserUrlService } from '@kbn/share-plugin/public'; +import { LocatorPublic } from '@kbn/share-plugin/public'; import { MatchedStateFromActor } from '@kbn/xstate-utils'; import { useActor } from '@xstate/react'; import React from 'react'; @@ -20,20 +20,28 @@ import { } from '../state_machines/observability_logs_explorer/src'; import { useKibanaContextForPlugin } from '../utils/use_kibana'; -export const ConnectedDatasetQualityLink = React.memo(() => { +export const ConnectedDatasetQualityLink = () => { const { services: { share: { url }, }, } = useKibanaContextForPlugin(); const [pageState] = useActor(useObservabilityLogsExplorerPageStateContext()); + const locator = url.locators.get<DataQualityLocatorParams>(DATA_QUALITY_LOCATOR_ID); - if (pageState.matches({ initialized: 'validLogsExplorerState' })) { - return <DatasetQualityLink urlService={url} pageState={pageState} />; - } else { - return <DatasetQualityLink urlService={url} />; + if (!locator) { + return null; } -}); + + return ( + <DatasetQualityLink + locator={locator} + pageState={ + pageState.matches({ initialized: 'validLogsExplorerState' }) ? pageState : undefined + } + /> + ); +}; type InitializedPageState = MatchedStateFromActor< ObservabilityLogsExplorerService, @@ -62,14 +70,12 @@ const constructLocatorParams = ( export const DatasetQualityLink = React.memo( ({ - urlService, + locator, pageState, }: { - urlService: BrowserUrlService; + locator: LocatorPublic<DataQualityLocatorParams>; pageState?: InitializedPageState; }) => { - const locator = urlService.locators.get<DataQualityLocatorParams>(DATA_QUALITY_LOCATOR_ID); - const locatorParams: DataQualityLocatorParams = pageState ? constructLocatorParams(pageState.context.logsExplorerState) : {}; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx index fb96bdbfc65f1..c3f91b3bf8660 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx @@ -70,8 +70,7 @@ const ProjectTopNav = () => { <EuiHeaderSectionItem> <EuiHeaderLinks gutterSize="xs"> <ConnectedDiscoverLink /> - <VerticalRule /> - <ConnectedDatasetQualityLink /> + <ConditionalVerticalRule Component={ConnectedDatasetQualityLink()} /> <VerticalRule /> <FeedbackLink /> <VerticalRule /> @@ -147,8 +146,7 @@ const ClassicTopNav = () => { <EuiHeaderSectionItem> <EuiHeaderLinks gutterSize="xs"> <ConnectedDiscoverLink /> - <VerticalRule /> - <ConnectedDatasetQualityLink /> + <ConditionalVerticalRule Component={ConnectedDatasetQualityLink()} /> <VerticalRule /> <AlertsPopover /> <VerticalRule /> @@ -165,3 +163,11 @@ const VerticalRule = styled.span` height: 20px; background-color: ${euiThemeVars.euiColorLightShade}; `; + +const ConditionalVerticalRule = ({ Component }: { Component: JSX.Element | null }) => + Component && ( + <> + <VerticalRule /> + {Component} + </> + ); From d9cd17bdbd47d66968bd5fda6fb32a08134fbc2d Mon Sep 17 00:00:00 2001 From: Ying Mao <ying.mao@elastic.co> Date: Tue, 15 Oct 2024 18:22:46 -0400 Subject: [PATCH 71/84] Adding model versions for all remaining so types without model versions (#195500) Resolves https://github.com/elastic/kibana/issues/184618 ## Summary Adds v1 schemas for all remaining Response Ops owned saved object types: * `connector_token` * `api_key_pending_invalidation` * `maintenance-window` * `rules-settings` ## To Verify 1. Run ES and Kibana on `main` and create saved objects for each of the above types: a. Create an OAuth ServiceNow ITOM connector to create a `connector_token` saved object b. Create a rule, let it run, and then delete the rule. This will create an `api_key_pending_invalidation` SO and 2 `rules-settings` SOs c. Create some maintenance windows, both with and without filters 2. Keep ES running and switch to this branch and restart Kibana. Then verify you can read and modify the existing SOs with no errors a. Test the ServiceNow ITOM connector, which should read the `connector_token` SO b. Modify the rules settings and then run a rule to ensure they're loaded with no errors c. Load the maintenance window UI and edit a MW Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../check_registered_types.test.ts | 8 +- .../actions/server/saved_objects/index.ts | 8 +- .../connector_token_model_versions.ts | 19 ++++ .../saved_objects/model_versions/index.ts | 1 + .../schemas/raw_connector_token/index.ts | 8 ++ .../schemas/raw_connector_token/v1.ts | 17 ++++ .../alerting/server/saved_objects/index.ts | 11 ++- ...key_pending_invalidation_model_versions.ts | 22 +++++ .../saved_objects/model_versions/index.ts | 3 + .../maintenance_window_model_versions.ts | 19 ++++ .../rules_settings_model_versions.ts | 19 ++++ .../raw_api_key_pending_invalidation/index.ts | 8 ++ .../raw_api_key_pending_invalidation/v1.ts | 13 +++ .../schemas/raw_maintenance_window/index.ts | 8 ++ .../schemas/raw_maintenance_window/v1.ts | 87 +++++++++++++++++++ .../schemas/raw_rules_settings/index.ts | 8 ++ .../schemas/raw_rules_settings/v1.ts | 31 +++++++ 17 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/actions/server/saved_objects/model_versions/connector_token_model_versions.ts create mode 100644 x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/index.ts create mode 100644 x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/v1.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/model_versions/api_key_pending_invalidation_model_versions.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/model_versions/maintenance_window_model_versions.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/model_versions/rules_settings_model_versions.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/index.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/v1.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/index.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/index.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/v1.ts diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 53198f9746cfa..e8c7d41c2a4fd 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -60,7 +60,7 @@ describe('checking migration metadata changes on all registered SO types', () => "action_task_params": "b50cb5c8a493881474918e8d4985e61374ca4c30", "ad_hoc_run_params": "d4e3c5c794151d0a4f5c71e886b2aa638da73ad2", "alert": "05b07040b12ff45ab642f47464e8a6c903cf7b86", - "api_key_pending_invalidation": "1399e87ca37b3d3a65d269c924eda70726cfe886", + "api_key_pending_invalidation": "8f5554d1984854011b8392d9a6f7ef985bcac03c", "apm-custom-dashboards": "b67128f78160c288bd7efe25b2da6e2afd5e82fc", "apm-indices": "8a2d68d415a4b542b26b0d292034a28ffac6fed4", "apm-server-schema": "58a8c6468edae3d1dc520f0134f59cf3f4fd7eff", @@ -83,7 +83,7 @@ describe('checking migration metadata changes on all registered SO types', () => "cloud-security-posture-settings": "e0f61c68bbb5e4cfa46ce8994fa001e417df51ca", "config": "179b3e2bc672626aafce3cf92093a113f456af38", "config-global": "8e8a134a2952df700d7d4ec51abb794bbd4cf6da", - "connector_token": "5a9ac29fe9c740eb114e9c40517245c71706b005", + "connector_token": "79977ea2cb1530ba7e315b95c1b5a524b622a6b3", "core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff", "csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654", "dashboard": "211e9ca30f5a95d5f3c27b1bf2b58e6cfa0c9ae9", @@ -131,7 +131,7 @@ describe('checking migration metadata changes on all registered SO types', () => "lens": "5cfa2c52b979b4f8df56dd13c477e152183468b9", "lens-ui-telemetry": "8c47a9e393861f76e268345ecbadfc8a5fb1e0bd", "links": "1dd432cc94619a513b75cec43660a50be7aadc90", - "maintenance-window": "d893544460abad56ff7a0e25b78f78776dfe10d1", + "maintenance-window": "bf36863f5577c2d22625258bdad906eeb4cccccc", "map": "76c71023bd198fb6b1163b31bafd926fe2ceb9da", "metrics-data-source": "81b69dc9830699d9ead5ac8dcb9264612e2a3c89", "metrics-explorer-view": "98cf395d0e87b89ab63f173eae16735584a8ff42", @@ -147,7 +147,7 @@ describe('checking migration metadata changes on all registered SO types', () => "policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352", "query": "501bece68f26fe561286a488eabb1a8ab12f1137", "risk-engine-configuration": "bab237d09c2e7189dddddcb1b28f19af69755efb", - "rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f", + "rules-settings": "ba57ef1881b3dcbf48fbfb28902d8f74442190b2", "sample-data-telemetry": "37441b12f5b0159c2d6d5138a494c9f440e950b5", "search": "0aa6eefb37edd3145be340a8b67779c2ca578b22", "search-session": "b2fcd840e12a45039ada50b1355faeafa39876d1", diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index a4d7886091fe5..102d2dda76225 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -13,7 +13,6 @@ import type { import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import { getOldestIdleActionTask } from '@kbn/task-manager-plugin/server'; import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import { actionTaskParamsModelVersions } from './model_versions'; import { actionMappings, actionTaskParamsMappings, connectorTokenMappings } from './mappings'; import { getActionsMigrations } from './actions_migrations'; import { getActionTaskParamsMigrations } from './action_task_params_migrations'; @@ -26,7 +25,11 @@ import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, CONNECTOR_TOKEN_SAVED_OBJECT_TYPE, } from '../constants/saved_objects'; -import { connectorModelVersions } from './model_versions'; +import { + actionTaskParamsModelVersions, + connectorModelVersions, + connectorTokenModelVersions, +} from './model_versions'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, @@ -121,6 +124,7 @@ export function setupSavedObjects( management: { importableAndExportable: false, }, + modelVersions: connectorTokenModelVersions, }); encryptedSavedObjects.registerType({ diff --git a/x-pack/plugins/actions/server/saved_objects/model_versions/connector_token_model_versions.ts b/x-pack/plugins/actions/server/saved_objects/model_versions/connector_token_model_versions.ts new file mode 100644 index 0000000000000..604e9866ca2de --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/model_versions/connector_token_model_versions.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawConnectorTokenSchemaV1 } from '../schemas/raw_connector_token'; + +export const connectorTokenModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + forwardCompatibility: rawConnectorTokenSchemaV1.extends({}, { unknowns: 'ignore' }), + create: rawConnectorTokenSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts b/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts index fdfc6adecd8e0..f573864ffbec4 100644 --- a/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts @@ -6,4 +6,5 @@ */ export { connectorModelVersions } from './connector_model_versions'; +export { connectorTokenModelVersions } from './connector_token_model_versions'; export { actionTaskParamsModelVersions } from './action_task_params_model_versions'; diff --git a/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/index.ts b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/index.ts new file mode 100644 index 0000000000000..66d20c740f8d2 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { rawConnectorTokenSchema as rawConnectorTokenSchemaV1 } from './v1'; diff --git a/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/v1.ts b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/v1.ts new file mode 100644 index 0000000000000..be91cf266b5bc --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector_token/v1.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const rawConnectorTokenSchema = schema.object({ + createdAt: schema.string(), + connectorId: schema.string(), + expiresAt: schema.string(), + token: schema.string(), + tokenType: schema.string(), + updatedAt: schema.string(), +}); diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index a3bb0b4f0afe8..8e76f28ff7fb8 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -28,7 +28,13 @@ import { RULES_SETTINGS_SAVED_OBJECT_TYPE, MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, } from '../../common'; -import { ruleModelVersions, adHocRunParamsModelVersions } from './model_versions'; +import { + adHocRunParamsModelVersions, + apiKeyPendingInvalidationModelVersions, + maintenanceWindowModelVersions, + ruleModelVersions, + rulesSettingsModelVersions, +} from './model_versions'; export const RULE_SAVED_OBJECT_TYPE = 'alert'; export const AD_HOC_RUN_SAVED_OBJECT_TYPE = 'ad_hoc_run_params'; @@ -145,6 +151,7 @@ export function setupSavedObjects( }, }, }, + modelVersions: apiKeyPendingInvalidationModelVersions, }); savedObjects.registerType({ @@ -153,6 +160,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'single', mappings: rulesSettingsMappings, + modelVersions: rulesSettingsModelVersions, }); savedObjects.registerType({ @@ -161,6 +169,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'multiple-isolated', mappings: maintenanceWindowMappings, + modelVersions: maintenanceWindowModelVersions, }); savedObjects.registerType({ diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/api_key_pending_invalidation_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/api_key_pending_invalidation_model_versions.ts new file mode 100644 index 0000000000000..0d6456a9b155a --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/api_key_pending_invalidation_model_versions.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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawApiKeyPendingInvalidationSchemaV1 } from '../schemas/raw_api_key_pending_invalidation'; + +export const apiKeyPendingInvalidationModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + forwardCompatibility: rawApiKeyPendingInvalidationSchemaV1.extends( + {}, + { unknowns: 'ignore' } + ), + create: rawApiKeyPendingInvalidationSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts index 89c4f3a3cd2bb..5c9a33b3b1714 100644 --- a/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts @@ -6,4 +6,7 @@ */ export { adHocRunParamsModelVersions } from './ad_hoc_run_params_model_versions'; +export { apiKeyPendingInvalidationModelVersions } from './api_key_pending_invalidation_model_versions'; +export { maintenanceWindowModelVersions } from './maintenance_window_model_versions'; export { ruleModelVersions } from './rule_model_versions'; +export { rulesSettingsModelVersions } from './rules_settings_model_versions'; diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/maintenance_window_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/maintenance_window_model_versions.ts new file mode 100644 index 0000000000000..dbfda11dc85fc --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/maintenance_window_model_versions.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawMaintenanceWindowSchemaV1 } from '../schemas/raw_maintenance_window'; + +export const maintenanceWindowModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + forwardCompatibility: rawMaintenanceWindowSchemaV1.extends({}, { unknowns: 'ignore' }), + create: rawMaintenanceWindowSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/rules_settings_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/rules_settings_model_versions.ts new file mode 100644 index 0000000000000..323238c43c01c --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/rules_settings_model_versions.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawRulesSettingsSchemaV1 } from '../schemas/raw_rules_settings'; + +export const rulesSettingsModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + forwardCompatibility: rawRulesSettingsSchemaV1.extends({}, { unknowns: 'ignore' }), + create: rawRulesSettingsSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/index.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/index.ts new file mode 100644 index 0000000000000..585c0601eb2a3 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { rawApiKeyPendingInvalidationSchema as rawApiKeyPendingInvalidationSchemaV1 } from './v1'; diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/v1.ts new file mode 100644 index 0000000000000..814b8bd099cd9 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_api_key_pending_invalidation/v1.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const rawApiKeyPendingInvalidationSchema = schema.object({ + apiKeyId: schema.string(), + createdAt: schema.string(), +}); diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/index.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/index.ts new file mode 100644 index 0000000000000..54ad09f251591 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { rawMaintenanceWindowSchema as rawMaintenanceWindowSchemaV1 } from './v1'; diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts new file mode 100644 index 0000000000000..66c5c432bb370 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts @@ -0,0 +1,87 @@ +/* + * 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 { FilterStateStore } from '@kbn/es-query'; + +export const alertsFilterQuerySchema = schema.object({ + kql: schema.string(), + filters: schema.arrayOf( + schema.object({ + query: schema.maybe(schema.recordOf(schema.string(), schema.any())), + meta: schema.recordOf(schema.string(), schema.any()), + $state: schema.maybe( + schema.object({ + store: schema.oneOf([ + schema.literal(FilterStateStore.APP_STATE), + schema.literal(FilterStateStore.GLOBAL_STATE), + ]), + }) + ), + }) + ), + dsl: schema.maybe(schema.string()), +}); + +const rRuleSchema = schema.object({ + dtstart: schema.string(), + tzid: schema.string(), + freq: schema.maybe( + schema.oneOf([ + schema.literal(0), + schema.literal(1), + schema.literal(2), + schema.literal(3), + schema.literal(4), + schema.literal(5), + schema.literal(6), + ]) + ), + until: schema.maybe(schema.string()), + count: schema.maybe(schema.number()), + interval: schema.maybe(schema.number()), + wkst: schema.maybe( + schema.oneOf([ + schema.literal('MO'), + schema.literal('TU'), + schema.literal('WE'), + schema.literal('TH'), + schema.literal('FR'), + schema.literal('SA'), + schema.literal('SU'), + ]) + ), + byweekday: schema.maybe(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))), + bymonth: schema.maybe(schema.number()), + bysetpos: schema.maybe(schema.number()), + bymonthday: schema.maybe(schema.number()), + byyearday: schema.maybe(schema.number()), + byweekno: schema.maybe(schema.number()), + byhour: schema.maybe(schema.number()), + byminute: schema.maybe(schema.number()), + bysecond: schema.maybe(schema.number()), +}); + +const rawMaintenanceWindowEventsSchema = schema.object({ + gte: schema.string(), + lte: schema.string(), +}); + +export const rawMaintenanceWindowSchema = schema.object({ + categoryIds: schema.maybe(schema.nullable(schema.arrayOf(schema.string()))), + createdAt: schema.string(), + createdBy: schema.nullable(schema.string()), + duration: schema.number(), + enabled: schema.boolean(), + events: schema.arrayOf(rawMaintenanceWindowEventsSchema), + expirationDate: schema.string(), + rRule: rRuleSchema, + scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)), + title: schema.string(), + updatedAt: schema.string(), + updatedBy: schema.nullable(schema.string()), +}); diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/index.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/index.ts new file mode 100644 index 0000000000000..293dccfcddf63 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { rawRulesSettingsSchema as rawRulesSettingsSchemaV1 } from './v1'; diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/v1.ts new file mode 100644 index 0000000000000..1e2aa60fca672 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rules_settings/v1.ts @@ -0,0 +1,31 @@ +/* + * 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'; + +export const rawRulesSettingsSchema = schema.object({ + flapping: schema.maybe( + schema.object({ + createdAt: schema.string(), + createdBy: schema.nullable(schema.string()), + enabled: schema.boolean(), + lookBackWindow: schema.number(), + statusChangeThreshold: schema.number(), + updatedAt: schema.string(), + updatedBy: schema.nullable(schema.string()), + }) + ), + queryDelay: schema.maybe( + schema.object({ + createdAt: schema.string(), + createdBy: schema.nullable(schema.string()), + delay: schema.number(), + updatedAt: schema.string(), + updatedBy: schema.nullable(schema.string()), + }) + ), +}); From 40bfd12cc55ebfb1641ef21133fb009c23b0106f Mon Sep 17 00:00:00 2001 From: Mark Hopkin <mark.hopkin@elastic.co> Date: Tue, 15 Oct 2024 23:27:50 +0100 Subject: [PATCH 72/84] [Entity Analytics] Allow task status to be "claiming" in disable/enable test (#196172) ## Summary Closes https://github.com/elastic/kibana/issues/196166 The test is checking that when we disable the risk engine, the risk ewngine task is registered but not actively running. This check originally checked if the task status was "idle". We have had a failure where the task status is "claiming", reading the docs about this task status (below) this is also an acceptable "non-running" status ``` // idle: Task Instance isn't being worked on // claiming: A Kibana instance has claimed ownership but hasn't started running // the Task Instance yet ``` --- .../init_and_status_apis.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts index 19a9bb85326fa..3224caa24d5e2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts @@ -19,6 +19,10 @@ import { } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; +const expectTaskIsNotRunning = (taskStatus?: string) => { + expect(['idle', 'claiming']).contain(taskStatus); +}; + export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const supertest = getService('supertest'); @@ -355,7 +359,7 @@ export default ({ getService }: FtrProviderContext) => { expect(status2.body.legacy_risk_engine_status).to.be('NOT_INSTALLED'); expect(status2.body.risk_engine_task_status?.runAt).to.be.a('string'); - expect(status2.body.risk_engine_task_status?.status).to.be('idle'); + expectTaskIsNotRunning(status2.body.risk_engine_task_status?.status); expect(status2.body.risk_engine_task_status?.startedAt).to.be(undefined); await riskEngineRoutes.disable(); @@ -373,7 +377,7 @@ export default ({ getService }: FtrProviderContext) => { expect(status4.body.legacy_risk_engine_status).to.be('NOT_INSTALLED'); expect(status4.body.risk_engine_task_status?.runAt).to.be.a('string'); - expect(status4.body.risk_engine_task_status?.status).to.be('idle'); + expectTaskIsNotRunning(status4.body.risk_engine_task_status?.status); expect(status4.body.risk_engine_task_status?.startedAt).to.be(undefined); }); @@ -394,7 +398,7 @@ export default ({ getService }: FtrProviderContext) => { expect(status2.body.legacy_risk_engine_status).to.be('NOT_INSTALLED'); expect(status2.body.risk_engine_task_status?.runAt).to.be.a('string'); - expect(status2.body.risk_engine_task_status?.status).to.be('idle'); + expectTaskIsNotRunning(status2.body.risk_engine_task_status?.status); expect(status2.body.risk_engine_task_status?.startedAt).to.be(undefined); }); }); From c448593d546f6200b0d2d35bce043bef521f41a6 Mon Sep 17 00:00:00 2001 From: Karen Grigoryan <karen.grigoryan@elastic.co> Date: Wed, 16 Oct 2024 01:18:50 +0200 Subject: [PATCH 73/84] [Security Solution][DQD] Add historical results tour guide (#196127) addresses #195971 This PR adds missing new historical results feature tour guide. ## Tour guide features: - ability to maintain visual presence while collapsing accordions in list-view - move from list-view to flyout view and back - seamlessly integrates with existing opening flyout and history tab functionality ## PR decisions with explanation: - data-tour-element has been introduced on select elements (like first actions of each first row) to avoid polluting every single element with data-test-subj. This way it's imho specific and semantically more clear what the elements are for. - early on I tried to control the anchoring with refs but some eui elements don't allow passing refs like EuiTab, so instead a more simpler and straightforward approach with dom selectors has been chosen - localStorage key name has been picked in accordance with other instances of usage `securitySolution.dataQualityDashboard.historicalResultsTour.v8.16.isActive` the name includes the full domain + the version when it's introduced. And since this tour step is a single step there is no need to stringify an object with `isTourActive` in and it's much simpler to just bake the activity state into the name and make the value just a boolean. ## UI Demo ### Anchor reposition demo (listview + flyout) https://github.com/user-attachments/assets/0f961c51-0e36-48ca-aab4-bef3b0d1269e ### List view tour guide try it + reload demo https://github.com/user-attachments/assets/ca1f5fda-ee02-4a48-827c-91df757a8ddf ### FlyOut Try It + reload demo https://github.com/user-attachments/assets/d0801ac3-1ed1-4e64-9d6b-3140b8402bdf ### Manual history tab selection path + reload demo https://github.com/user-attachments/assets/34dbb447-2fd6-4dc0-a4f5-682c9c65cc8b ### Manual open history view path + reload demo https://github.com/user-attachments/assets/945dd042-fc12-476e-8d23-f48c9ded9f65 ### Dismiss list view tour guide + reload demo https://github.com/user-attachments/assets/d20d1416-827f-46f2-9161-a3c0a8cbd932 ### Dismiss FlyOut tour guide + reload demo https://github.com/user-attachments/assets/8f085f59-20a9-49f0-b5b3-959c4719f5cb ### Serverless empty pattern handling + reposition demo https://github.com/user-attachments/assets/4af5939e-663c-4439-a3fc-deff2d4de7e4 --- .../indices_details/constants.ts | 9 + .../index.tsx | 28 + .../indices_details/index.test.tsx | 79 ++- .../indices_details/index.tsx | 48 +- .../indices_details/pattern/constants.ts | 2 + .../historical_results_tour/index.test.tsx | 105 ++++ .../pattern/historical_results_tour/index.tsx | 80 +++ .../historical_results_tour/translations.ts | 30 + .../indices_details/pattern/index.test.tsx | 549 +++++++++++++++++- .../indices_details/pattern/index.tsx | 85 ++- .../pattern/index_check_flyout/index.test.tsx | 185 +++++- .../pattern/index_check_flyout/index.tsx | 48 +- .../pattern/summary_table/index.tsx | 3 + .../summary_table/utils/columns.test.tsx | 54 ++ .../pattern/summary_table/utils/columns.tsx | 7 + .../mock_auditbeat_pattern_rollup.ts | 18 + 16 files changed, 1304 insertions(+), 26 deletions(-) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/constants.ts create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/hooks/use_is_historical_results_tour_active/index.tsx create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.test.tsx create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.tsx create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/constants.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/constants.ts new file mode 100644 index 0000000000000..68c373217a4b4 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const HISTORICAL_RESULTS_TOUR_IS_DISMISSED_STORAGE_KEY = + 'securitySolution.dataQualityDashboard.historicalResultsTour.v8.16.isDismissed'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/hooks/use_is_historical_results_tour_active/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/hooks/use_is_historical_results_tour_active/index.tsx new file mode 100644 index 0000000000000..572bf7023dada --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/hooks/use_is_historical_results_tour_active/index.tsx @@ -0,0 +1,28 @@ +/* + * 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 { useCallback } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; + +import { HISTORICAL_RESULTS_TOUR_IS_DISMISSED_STORAGE_KEY } from '../../constants'; + +export const useIsHistoricalResultsTourActive = () => { + const [isTourDismissed, setIsTourDismissed] = useLocalStorage<boolean>( + HISTORICAL_RESULTS_TOUR_IS_DISMISSED_STORAGE_KEY, + false + ); + + const isTourActive = !isTourDismissed; + const setIsTourActive = useCallback( + (active: boolean) => { + setIsTourDismissed(!active); + }, + [setIsTourDismissed] + ); + + return [isTourActive, setIsTourActive] as const; +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx index d5aaa1eea19ae..b3d296c5a30db 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx @@ -6,12 +6,15 @@ */ import numeral from '@elastic/numeral'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import React from 'react'; import { EMPTY_STAT } from '../../constants'; import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { + auditbeatWithAllResults, + emptyAuditbeatPatternRollup, +} from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; import { TestDataQualityProviders, @@ -19,6 +22,8 @@ import { } from '../../mock/test_providers/test_providers'; import { PatternRollup } from '../../types'; import { Props, IndicesDetails } from '.'; +import userEvent from '@testing-library/user-event'; +import { HISTORICAL_RESULTS_TOUR_IS_DISMISSED_STORAGE_KEY } from './constants'; const defaultBytesFormat = '0,0.[0]b'; const formatBytes = (value: number | undefined) => @@ -29,15 +34,22 @@ const formatNumber = (value: number | undefined) => value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; const ilmPhases = ['hot', 'warm', 'unmanaged']; -const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; +const patterns = [ + 'test-empty-pattern-*', + '.alerts-security.alerts-default', + 'auditbeat-*', + 'packetbeat-*', +]; const patternRollups: Record<string, PatternRollup> = { + 'test-empty-pattern-*': { ...emptyAuditbeatPatternRollup, pattern: 'test-empty-pattern-*' }, '.alerts-security.alerts-default': alertIndexWithAllResults, 'auditbeat-*': auditbeatWithAllResults, 'packetbeat-*': packetbeatNoResults, }; const patternIndexNames: Record<string, string[]> = { + 'test-empty-pattern-*': [], 'auditbeat-*': [ '.ds-auditbeat-8.6.1-2023.02.07-000001', 'auditbeat-custom-empty-index-1', @@ -58,6 +70,7 @@ const defaultProps: Props = { describe('IndicesDetails', () => { beforeEach(async () => { jest.clearAllMocks(); + localStorage.removeItem(HISTORICAL_RESULTS_TOUR_IS_DISMISSED_STORAGE_KEY); render( <TestExternalProviders> @@ -74,10 +87,64 @@ describe('IndicesDetails', () => { }); describe('rendering patterns', () => { - patterns.forEach((pattern) => { - test(`it renders the ${pattern} pattern`, () => { - expect(screen.getByTestId(`${pattern}PatternPanel`)).toBeInTheDocument(); + test.each(patterns)('it renders the %s pattern', (pattern) => { + expect(screen.getByTestId(`${pattern}PatternPanel`)).toBeInTheDocument(); + }); + }); + + describe('tour', () => { + test('it renders the tour wrapping view history button of first row of first non-empty pattern', async () => { + const wrapper = await screen.findByTestId('historicalResultsTour'); + const button = within(wrapper).getByRole('button', { name: 'View history' }); + expect(button).toBeInTheDocument(); + expect(button).toHaveAttribute('data-tour-element', patterns[1]); + + expect( + screen.getByRole('dialog', { name: 'Introducing data quality history' }) + ).toBeInTheDocument(); + }); + + describe('when the tour is dismissed', () => { + test('it hides the tour and persists in localStorage', async () => { + const wrapper = await screen.findByRole('dialog', { + name: 'Introducing data quality history', + }); + + const button = within(wrapper).getByRole('button', { name: 'Close' }); + + await userEvent.click(button); + + await waitFor(() => expect(screen.queryByTestId('historicalResultsTour')).toBeNull()); + + expect(localStorage.getItem(HISTORICAL_RESULTS_TOUR_IS_DISMISSED_STORAGE_KEY)).toEqual( + 'true' + ); }); }); + + describe('when the first pattern is toggled', () => { + test('it renders the tour wrapping view history button of first row of second non-empty pattern', async () => { + const firstNonEmptyPatternAccordionWrapper = await screen.findByTestId( + `${patterns[1]}PatternPanel` + ); + const accordionToggle = within(firstNonEmptyPatternAccordionWrapper).getByRole('button', { + name: /Pass/, + }); + await userEvent.click(accordionToggle); + + const secondPatternAccordionWrapper = screen.getByTestId(`${patterns[2]}PatternPanel`); + const historicalResultsWrapper = await within(secondPatternAccordionWrapper).findByTestId( + 'historicalResultsTour' + ); + const button = within(historicalResultsWrapper).getByRole('button', { + name: 'View history', + }); + expect(button).toHaveAttribute('data-tour-element', patterns[2]); + + expect( + screen.getByRole('dialog', { name: 'Introducing data quality history' }) + ).toBeInTheDocument(); + }, 10000); + }); }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx index fd565d8fc7637..b3b708291a983 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx @@ -6,13 +6,14 @@ */ import { EuiFlexItem } from '@elastic/eui'; -import React from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import styled from 'styled-components'; import { useResultsRollupContext } from '../../contexts/results_rollup_context'; import { Pattern } from './pattern'; import { SelectedIndex } from '../../types'; import { useDataQualityContext } from '../../data_quality_context'; +import { useIsHistoricalResultsTourActive } from './hooks/use_is_historical_results_tour_active'; const StyledPatternWrapperFlexItem = styled(EuiFlexItem)` margin-bottom: ${({ theme }) => theme.eui.euiSize}; @@ -34,6 +35,41 @@ const IndicesDetailsComponent: React.FC<Props> = ({ const { patternRollups, patternIndexNames } = useResultsRollupContext(); const { patterns } = useDataQualityContext(); + const [isTourActive, setIsTourActive] = useIsHistoricalResultsTourActive(); + + const handleDismissTour = useCallback(() => { + setIsTourActive(false); + }, [setIsTourActive]); + + const [openPatterns, setOpenPatterns] = useState< + Array<{ name: string; isOpen: boolean; isEmpty: boolean }> + >(() => { + return patterns.map((pattern) => ({ name: pattern, isOpen: true, isEmpty: false })); + }); + + const handleAccordionToggle = useCallback( + (patternName: string, isOpen: boolean, isEmpty: boolean) => { + setOpenPatterns((prevOpenPatterns) => { + return prevOpenPatterns.map((p) => + p.name === patternName ? { ...p, isOpen, isEmpty } : p + ); + }); + }, + [] + ); + + const firstOpenNonEmptyPattern = openPatterns.find((pattern) => { + return pattern.isOpen && !pattern.isEmpty; + })?.name; + + const [openPatternsUpdatedAt, setOpenPatternsUpdatedAt] = useState<number>(Date.now()); + + useEffect(() => { + if (firstOpenNonEmptyPattern) { + setOpenPatternsUpdatedAt(Date.now()); + } + }, [openPatterns, firstOpenNonEmptyPattern]); + return ( <div data-test-subj="indicesDetails"> {patterns.map((pattern) => ( @@ -44,6 +80,16 @@ const IndicesDetailsComponent: React.FC<Props> = ({ patternRollup={patternRollups[pattern]} chartSelectedIndex={chartSelectedIndex} setChartSelectedIndex={setChartSelectedIndex} + isTourActive={isTourActive} + isFirstOpenNonEmptyPattern={pattern === firstOpenNonEmptyPattern} + onAccordionToggle={handleAccordionToggle} + onDismissTour={handleDismissTour} + // TODO: remove this hack when EUI popover is fixed + // https://github.com/elastic/eui/issues/5226 + // + // this information is used to force the tour guide popover to reposition + // when surrounding accordions get toggled and affect the layout + {...(pattern === firstOpenNonEmptyPattern && { openPatternsUpdatedAt })} /> </StyledPatternWrapperFlexItem> ))} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/constants.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/constants.ts index 4bab5938cf98b..a02eccb3e81a4 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/constants.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/constants.ts @@ -9,3 +9,5 @@ export const MIN_PAGE_SIZE = 10; export const HISTORY_TAB_ID = 'history'; export const LATEST_CHECK_TAB_ID = 'latest_check'; + +export const HISTORICAL_RESULTS_TOUR_SELECTOR_KEY = 'data-tour-element'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.test.tsx new file mode 100644 index 0000000000000..53f2e059072c8 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.test.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { HISTORICAL_RESULTS_TOUR_SELECTOR_KEY } from '../constants'; +import { HistoricalResultsTour } from '.'; +import { INTRODUCING_DATA_QUALITY_HISTORY, VIEW_PAST_RESULTS } from './translations'; + +const anchorSelectorValue = 'test-anchor'; + +describe('HistoricalResultsTour', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('given no anchor element', () => { + it('does not render the tour step', () => { + render( + <HistoricalResultsTour + anchorSelectorValue={anchorSelectorValue} + onTryIt={jest.fn()} + isOpen={true} + onDismissTour={jest.fn()} + /> + ); + + expect(screen.queryByText(INTRODUCING_DATA_QUALITY_HISTORY)).not.toBeInTheDocument(); + }); + }); + + describe('given an anchor element', () => { + beforeEach(() => { + // eslint-disable-next-line no-unsanitized/property + document.body.innerHTML = `<div ${HISTORICAL_RESULTS_TOUR_SELECTOR_KEY}="${anchorSelectorValue}"></div>`; + }); + + describe('when isOpen is true', () => { + const onTryIt = jest.fn(); + const onDismissTour = jest.fn(); + beforeEach(() => { + render( + <HistoricalResultsTour + anchorSelectorValue={anchorSelectorValue} + onTryIt={onTryIt} + isOpen={true} + onDismissTour={onDismissTour} + /> + ); + }); + it('renders the tour step', async () => { + expect( + await screen.findByRole('dialog', { name: INTRODUCING_DATA_QUALITY_HISTORY }) + ).toBeInTheDocument(); + expect(screen.getByText(INTRODUCING_DATA_QUALITY_HISTORY)).toBeInTheDocument(); + expect(screen.getByText(VIEW_PAST_RESULTS)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Close/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Try It/i })).toBeInTheDocument(); + + const historicalResultsTour = screen.getByTestId('historicalResultsTour'); + expect(historicalResultsTour.querySelector('[data-tour-element]')).toHaveAttribute( + 'data-tour-element', + anchorSelectorValue + ); + }); + + describe('when the close button is clicked', () => { + it('calls dismissTour', async () => { + await userEvent.click(await screen.findByRole('button', { name: /Close/i })); + expect(onDismissTour).toHaveBeenCalledTimes(1); + }); + }); + + describe('when the try it button is clicked', () => { + it('calls onTryIt', async () => { + await userEvent.click(await screen.findByRole('button', { name: /Try It/i })); + expect(onTryIt).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('when isOpen is false', () => { + it('does not render the tour step', async () => { + render( + <HistoricalResultsTour + anchorSelectorValue={anchorSelectorValue} + onTryIt={jest.fn()} + isOpen={false} + onDismissTour={jest.fn()} + /> + ); + + await waitFor(() => + expect(screen.queryByText(INTRODUCING_DATA_QUALITY_HISTORY)).not.toBeInTheDocument() + ); + }); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.tsx new file mode 100644 index 0000000000000..5e63379d17375 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/index.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useEffect, useState } from 'react'; +import { EuiButton, EuiButtonEmpty, EuiText, EuiTourStep } from '@elastic/eui'; +import styled from 'styled-components'; + +import { HISTORICAL_RESULTS_TOUR_SELECTOR_KEY } from '../constants'; +import { CLOSE, INTRODUCING_DATA_QUALITY_HISTORY, TRY_IT, VIEW_PAST_RESULTS } from './translations'; + +export interface Props { + anchorSelectorValue: string; + isOpen: boolean; + onTryIt: () => void; + onDismissTour: () => void; + zIndex?: number; +} + +const StyledText = styled(EuiText)` + margin-block-start: -10px; +`; + +export const HistoricalResultsTour: FC<Props> = ({ + anchorSelectorValue, + onTryIt, + isOpen, + onDismissTour, + zIndex, +}) => { + const [anchorElement, setAnchorElement] = useState<HTMLElement>(); + + useEffect(() => { + const element = document.querySelector<HTMLElement>( + `[${HISTORICAL_RESULTS_TOUR_SELECTOR_KEY}="${anchorSelectorValue}"]` + ); + + if (!element) { + return; + } + + setAnchorElement(element); + }, [anchorSelectorValue]); + + if (!isOpen || !anchorElement) { + return null; + } + + return ( + <EuiTourStep + content={ + <StyledText size="s"> + <p>{VIEW_PAST_RESULTS}</p> + </StyledText> + } + data-test-subj="historicalResultsTour" + isStepOpen={isOpen} + minWidth={283} + onFinish={onDismissTour} + step={1} + stepsTotal={1} + title={INTRODUCING_DATA_QUALITY_HISTORY} + anchorPosition="rightUp" + repositionOnScroll + anchor={anchorElement} + zIndex={zIndex} + footerAction={[ + <EuiButtonEmpty size="xs" color="text" onClick={onDismissTour}> + {CLOSE} + </EuiButtonEmpty>, + <EuiButton color="success" size="s" onClick={onTryIt}> + {TRY_IT} + </EuiButton>, + ]} + /> + ); +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/translations.ts new file mode 100644 index 0000000000000..d8f81aa288baa --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/historical_results_tour/translations.ts @@ -0,0 +1,30 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const CLOSE = i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.close', { + defaultMessage: 'Close', +}); + +export const TRY_IT = i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.tryIt', { + defaultMessage: 'Try it', +}); + +export const INTRODUCING_DATA_QUALITY_HISTORY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.introducingDataQualityHistory', + { + defaultMessage: 'Introducing data quality history', + } +); + +export const VIEW_PAST_RESULTS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.viewPastResults', + { + defaultMessage: 'View past results', + } +); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx index a165378df80ed..eb6116c3276f9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx @@ -6,19 +6,23 @@ */ import React from 'react'; -import { act, render, screen, within } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import { TestDataQualityProviders, TestExternalProviders, } from '../../../mock/test_providers/test_providers'; import { Pattern } from '.'; -import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { + auditbeatWithAllResults, + emptyAuditbeatPatternRollup, +} from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; import { useIlmExplain } from './hooks/use_ilm_explain'; import { useStats } from './hooks/use_stats'; import { ERROR_LOADING_METADATA_TITLE, LOADING_STATS } from './translations'; import { useHistoricalResults } from './hooks/use_historical_results'; import { getHistoricalResultStub } from '../../../stub/get_historical_result_stub'; +import userEvent from '@testing-library/user-event'; const pattern = 'auditbeat-*'; @@ -81,6 +85,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -95,6 +103,157 @@ describe('pattern', () => { expect(screen.getByTestId('summaryTable')).toBeInTheDocument(); }); + describe('onAccordionToggle', () => { + describe('by default', () => { + describe('when no summary table items are available', () => { + it('invokes the onAccordionToggle function with the pattern name, isOpen as true and isEmpty as true', async () => { + const onAccordionToggle = jest.fn(); + + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: null, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: null, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={emptyAuditbeatPatternRollup} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={[]} + pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={onAccordionToggle} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const accordionToggle = await screen.findByRole('button', { + name: 'auditbeat-* Incompatible fields 0 Indices checked 0 Indices 0 Size 0B Docs 0', + }); + + expect(onAccordionToggle).toHaveBeenCalledTimes(1); + + await userEvent.click(accordionToggle); + + expect(onAccordionToggle).toHaveBeenCalledTimes(2); + expect(onAccordionToggle).toHaveBeenCalledWith(pattern, true, true); + }); + }); + + describe('when summary table items are available', () => { + it('invokes the onAccordionToggle function with the pattern name, isOpen as true and isEmpty as false', async () => { + const onAccordionToggle = jest.fn(); + + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={onAccordionToggle} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const accordionToggle = screen.getByRole('button', { + name: 'Fail auditbeat-* hot (1) unmanaged (2) Incompatible fields 4 Indices checked 3 Indices 3 Size 17.9MB Docs 19,127', + }); + + expect(onAccordionToggle).toHaveBeenCalledTimes(1); + + await userEvent.click(accordionToggle); + + expect(onAccordionToggle).toHaveBeenCalledTimes(2); + expect(onAccordionToggle).toHaveBeenCalledWith(pattern, true, false); + }); + }); + }); + + describe('when the accordion is toggled', () => { + it('calls the onAccordionToggle function with current open state and current empty state', async () => { + const onAccordionToggle = jest.fn(); + + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={onAccordionToggle} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const accordionToggle = screen.getByRole('button', { + name: 'Fail auditbeat-* hot (1) unmanaged (2) Incompatible fields 4 Indices checked 3 Indices 3 Size 17.9MB Docs 19,127', + }); + + expect(onAccordionToggle).toHaveBeenCalledTimes(1); + expect(onAccordionToggle).toHaveBeenCalledWith(pattern, true, false); + + await userEvent.click(accordionToggle); + + expect(onAccordionToggle).toHaveBeenCalledTimes(2); + expect(onAccordionToggle).toHaveBeenLastCalledWith(pattern, false, false); + + await userEvent.click(accordionToggle); + + expect(onAccordionToggle).toHaveBeenCalledTimes(3); + expect(onAccordionToggle).toHaveBeenCalledWith(pattern, true, false); + }); + }); + }); + describe('remote clusters callout', () => { describe('when the pattern includes a colon', () => { it('it renders the remote clusters callout', () => { @@ -107,6 +266,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={undefined} pattern={'remote:*'} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -127,6 +290,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={undefined} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -155,6 +322,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -182,6 +353,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -215,6 +390,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -248,6 +427,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -292,6 +475,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -306,7 +493,7 @@ describe('pattern', () => { name: 'Check now', }); - await act(async () => checkNowButton.click()); + await userEvent.click(checkNowButton); // assert expect(checkIndex).toHaveBeenCalledTimes(1); @@ -370,6 +557,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -384,7 +575,7 @@ describe('pattern', () => { name: 'View history', }); - await act(async () => viewHistoryButton.click()); + await userEvent.click(viewHistoryButton); // assert expect(fetchHistoricalResults).toHaveBeenCalledTimes(1); @@ -444,6 +635,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -458,11 +653,11 @@ describe('pattern', () => { name: 'View history', }); - await act(async () => viewHistoryButton.click()); + await userEvent.click(viewHistoryButton); const closeButton = screen.getByRole('button', { name: 'Close this dialog' }); - await act(async () => closeButton.click()); + await userEvent.click(closeButton); // assert expect(screen.queryByTestId('indexCheckFlyout')).not.toBeInTheDocument(); @@ -504,6 +699,10 @@ describe('pattern', () => { setChartSelectedIndex={jest.fn()} indexNames={Object.keys(auditbeatWithAllResults.stats!)} pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} /> </TestDataQualityProviders> </TestExternalProviders> @@ -533,4 +732,342 @@ describe('pattern', () => { }); }); }); + + describe('Tour', () => { + describe('when isTourActive and isFirstOpenNonEmptyPattern', () => { + it('renders the tour near the first row history view button', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={true} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={true} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const rows = screen.getAllByRole('row'); + // skipping the first row which is the header + const firstBodyRow = within(rows[1]); + + const tourWrapper = await firstBodyRow.findByTestId('historicalResultsTour'); + + expect( + within(tourWrapper).getByRole('button', { name: 'View history' }) + ).toBeInTheDocument(); + + expect( + screen.getByRole('dialog', { name: 'Introducing data quality history' }) + ).toBeInTheDocument(); + }); + + describe('when accordion is collapsed', () => { + it('hides the tour', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={true} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={true} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + expect(await screen.findByTestId('historicalResultsTour')).toBeInTheDocument(); + + const accordionToggle = screen.getByRole('button', { + name: 'Fail auditbeat-* hot (1) unmanaged (2) Incompatible fields 4 Indices checked 3 Indices 3 Size 17.9MB Docs 19,127', + }); + + await userEvent.click(accordionToggle); + + expect(screen.queryByTestId('historicalResultsTour')).not.toBeInTheDocument(); + }, 10000); + }); + + describe('when the tour close button is clicked', () => { + it('invokes onDismissTour', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + const onDismissTour = jest.fn(); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={true} + onDismissTour={onDismissTour} + isFirstOpenNonEmptyPattern={true} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const tourDialog = await screen.findByRole('dialog', { + name: 'Introducing data quality history', + }); + + const closeButton = within(tourDialog).getByRole('button', { name: 'Close' }); + + await userEvent.click(closeButton); + + expect(onDismissTour).toHaveBeenCalledTimes(1); + }); + }); + + describe('when the tour tryIt action is clicked', () => { + it('opens the flyout with history tab and invokes onDismissTour', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + const onDismissTour = jest.fn(); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={true} + onDismissTour={onDismissTour} + isFirstOpenNonEmptyPattern={true} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const tourDialog = await screen.findByRole('dialog', { + name: 'Introducing data quality history', + }); + + const tryItButton = within(tourDialog).getByRole('button', { name: 'Try it' }); + + await userEvent.click(tryItButton); + + expect(onDismissTour).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('indexCheckFlyout')).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Latest Check' })).toHaveAttribute( + 'aria-selected', + 'false' + ); + expect(screen.getByRole('tab', { name: 'History' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + }); + }); + + describe('when latest latest check flyout tab is opened', () => { + it('hides the tour in listview and shows in flyout', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + const onDismissTour = jest.fn(); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={true} + onDismissTour={onDismissTour} + isFirstOpenNonEmptyPattern={true} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const rows = screen.getAllByRole('row'); + // skipping the first row which is the header + const firstBodyRow = within(rows[1]); + + expect(await firstBodyRow.findByTestId('historicalResultsTour')).toBeInTheDocument(); + expect( + screen.getByRole('dialog', { name: 'Introducing data quality history' }) + ).toBeInTheDocument(); + + const checkNowButton = firstBodyRow.getByRole('button', { + name: 'Check now', + }); + await userEvent.click(checkNowButton); + + expect(screen.getByTestId('indexCheckFlyout')).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Latest Check' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + expect(screen.getByRole('tab', { name: 'History' })).toHaveAttribute( + 'aria-selected', + 'false' + ); + + expect(firstBodyRow.queryByTestId('historicalResultsTour')).not.toBeInTheDocument(); + + const tabWrapper = await screen.findByRole('tab', { name: 'History' }); + await waitFor(() => + expect( + tabWrapper.closest('[data-test-subj="historicalResultsTour"]') + ).toBeInTheDocument() + ); + + expect(onDismissTour).not.toHaveBeenCalled(); + }, 10000); + }); + }); + + describe('when not isFirstOpenNonEmptyPattern', () => { + it('does not render the tour', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={true} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={false} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + expect(screen.queryByTestId('historicalResultsTour')).not.toBeInTheDocument(); + }); + }); + + describe('when not isTourActive', () => { + it('does not render the tour', async () => { + (useIlmExplain as jest.Mock).mockReturnValue({ + error: null, + ilmExplain: auditbeatWithAllResults.ilmExplain, + loading: false, + }); + + (useStats as jest.Mock).mockReturnValue({ + stats: auditbeatWithAllResults.stats, + error: null, + loading: false, + }); + + render( + <TestExternalProviders> + <TestDataQualityProviders> + <Pattern + patternRollup={auditbeatWithAllResults} + chartSelectedIndex={null} + setChartSelectedIndex={jest.fn()} + indexNames={Object.keys(auditbeatWithAllResults.stats!)} + pattern={pattern} + isTourActive={false} + onDismissTour={jest.fn()} + isFirstOpenNonEmptyPattern={true} + onAccordionToggle={jest.fn()} + /> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + expect(screen.queryByTestId('historicalResultsTour')).not.toBeInTheDocument(); + }); + }); + }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx index 30c4aa8755a9c..a51f521eca169 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx @@ -35,6 +35,7 @@ import { getPageIndex } from './utils/get_page_index'; import { useAbortControllerRef } from '../../../hooks/use_abort_controller_ref'; import { useHistoricalResults } from './hooks/use_historical_results'; import { HistoricalResultsContext } from './contexts/historical_results_context'; +import { HistoricalResultsTour } from './historical_results_tour'; const EMPTY_INDEX_NAMES: string[] = []; @@ -44,6 +45,11 @@ interface Props { patternRollup: PatternRollup | undefined; chartSelectedIndex: SelectedIndex | null; setChartSelectedIndex: (selectedIndex: SelectedIndex | null) => void; + isTourActive: boolean; + isFirstOpenNonEmptyPattern: boolean; + onAccordionToggle: (patternName: string, isOpen: boolean, isEmpty: boolean) => void; + onDismissTour: () => void; + openPatternsUpdatedAt?: number; } const PatternComponent: React.FC<Props> = ({ @@ -52,6 +58,11 @@ const PatternComponent: React.FC<Props> = ({ patternRollup, chartSelectedIndex, setChartSelectedIndex, + isTourActive, + isFirstOpenNonEmptyPattern, + onAccordionToggle, + onDismissTour, + openPatternsUpdatedAt, }) => { const { historicalResultsState, fetchHistoricalResults } = useHistoricalResults(); const historicalResultsContextValue = useMemo( @@ -124,6 +135,35 @@ const PatternComponent: React.FC<Props> = ({ ] ); + const [isAccordionOpen, setIsAccordionOpen] = useState(true); + + const isAccordionOpenRef = useRef(isAccordionOpen); + useEffect(() => { + isAccordionOpenRef.current = isAccordionOpen; + }, [isAccordionOpen]); + + useEffect(() => { + // this use effect syncs isEmpty state with the parent component + // + // we do not add isAccordionOpen to the dependency array because + // it is already handled by handleAccordionToggle + // so we don't want to additionally trigger this useEffect when isAccordionOpen changes + // because it's confusing and unnecessary + // that's why we use ref here to keep separation of concerns + onAccordionToggle(pattern, isAccordionOpenRef.current, items.length === 0); + }, [items.length, onAccordionToggle, pattern]); + + const handleAccordionToggle = useCallback( + (isOpen: boolean) => { + const isEmpty = items.length === 0; + setIsAccordionOpen(isOpen); + onAccordionToggle(pattern, isOpen, isEmpty); + }, + [items.length, onAccordionToggle, pattern] + ); + + const firstRow = items[0]; + const handleFlyoutClose = useCallback(() => { setExpandedIndexName(null); }, []); @@ -153,6 +193,9 @@ const PatternComponent: React.FC<Props> = ({ const handleFlyoutViewCheckHistoryAction = useCallback( (indexName: string) => { + if (isTourActive) { + onDismissTour(); + } fetchHistoricalResults({ abortController: flyoutViewCheckHistoryAbortControllerRef.current, indexName, @@ -160,9 +203,16 @@ const PatternComponent: React.FC<Props> = ({ setExpandedIndexName(indexName); setInitialFlyoutTabId(HISTORY_TAB_ID); }, - [fetchHistoricalResults, flyoutViewCheckHistoryAbortControllerRef] + [fetchHistoricalResults, flyoutViewCheckHistoryAbortControllerRef, isTourActive, onDismissTour] ); + const handleOpenFlyoutHistoryTab = useCallback(() => { + const firstItemIndexName = firstRow?.indexName; + if (firstItemIndexName) { + handleFlyoutViewCheckHistoryAction(firstItemIndexName); + } + }, [firstRow?.indexName, handleFlyoutViewCheckHistoryAction]); + useEffect(() => { const newIndexNames = getIndexNames({ stats, ilmExplain, ilmPhases, isILMAvailable }); const newDocsCount = getPatternDocsCount({ indexNames: newIndexNames, stats }); @@ -270,7 +320,8 @@ const PatternComponent: React.FC<Props> = ({ <HistoricalResultsContext.Provider value={historicalResultsContextValue}> <PatternAccordion id={patternComponentAccordionId} - initialIsOpen={true} + forceState={isAccordionOpen ? 'open' : 'closed'} + onToggle={handleAccordionToggle} buttonElement="div" buttonContent={ <PatternSummary @@ -308,6 +359,34 @@ const PatternComponent: React.FC<Props> = ({ {!loading && error == null && ( <div ref={containerRef}> + <HistoricalResultsTour + // this is a hack to force popover anchor position recalculation + // when the first open non-empty pattern layout changes due to other + // patterns being opened/closed + // It's a bug on Eui side + // + // TODO: remove this hack when EUI popover is fixed + // https://github.com/elastic/eui/issues/5226 + {...(isFirstOpenNonEmptyPattern && { key: openPatternsUpdatedAt })} + anchorSelectorValue={pattern} + onTryIt={handleOpenFlyoutHistoryTab} + isOpen={ + isTourActive && + !isFlyoutVisible && + isFirstOpenNonEmptyPattern && + isAccordionOpen + } + onDismissTour={onDismissTour} + // Only set zIndex when the tour is in list view (not in flyout) + // + // 1 less than the z-index of the left navigation + // 5 less than the z-index of the timeline + // + // + // TODO this hack should be removed when we properly set z-indexes + // in the timeline and left navigation + zIndex={998} + /> <SummaryTable getTableColumns={getSummaryTableColumns} items={items} @@ -334,6 +413,8 @@ const PatternComponent: React.FC<Props> = ({ ilmExplain={ilmExplain} stats={stats} onClose={handleFlyoutClose} + onDismissTour={onDismissTour} + isTourActive={isTourActive} /> ) : null} </HistoricalResultsContext.Provider> diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx index 7b63f712a99da..e73fd4c2d610d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { IndexCheckFlyout } from '.'; @@ -41,6 +41,8 @@ describe('IndexCheckFlyout', () => { pattern="auditbeat-*" patternRollup={auditbeatWithAllResults} stats={mockStats} + onDismissTour={jest.fn()} + isTourActive={false} /> </TestHistoricalResultsProvider> </TestDataQualityProviders> @@ -97,6 +99,8 @@ describe('IndexCheckFlyout', () => { patternRollup={auditbeatWithAllResults} stats={mockStats} initialSelectedTabId="latest_check" + isTourActive={false} + onDismissTour={jest.fn()} /> </TestHistoricalResultsProvider> </TestDataQualityProviders> @@ -129,6 +133,8 @@ describe('IndexCheckFlyout', () => { patternRollup={auditbeatWithAllResults} stats={mockStats} initialSelectedTabId="latest_check" + isTourActive={false} + onDismissTour={jest.fn()} /> </TestHistoricalResultsProvider> </TestDataQualityProviders> @@ -175,6 +181,8 @@ describe('IndexCheckFlyout', () => { patternRollup={auditbeatWithAllResults} stats={mockStats} initialSelectedTabId="latest_check" + onDismissTour={jest.fn()} + isTourActive={false} /> </TestHistoricalResultsProvider> </TestDataQualityProviders> @@ -207,4 +215,179 @@ describe('IndexCheckFlyout', () => { expect(screen.getByTestId('historicalResults')).toBeInTheDocument(); }); }); + + describe('Tour guide', () => { + describe('when in Latest Check tab and isTourActive', () => { + it('should render the tour guide near history tab with proper data-tour-element attribute', async () => { + const pattern = 'auditbeat-*'; + render( + <TestExternalProviders> + <TestDataQualityProviders> + <TestHistoricalResultsProvider> + <IndexCheckFlyout + ilmExplain={mockIlmExplain} + indexName="auditbeat-custom-index-1" + onClose={jest.fn()} + pattern={pattern} + patternRollup={auditbeatWithAllResults} + stats={mockStats} + initialSelectedTabId="latest_check" + onDismissTour={jest.fn()} + isTourActive={true} + /> + </TestHistoricalResultsProvider> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const historyTab = screen.getByRole('tab', { name: 'History' }); + const latestCheckTab = screen.getByRole('tab', { name: 'Latest Check' }); + + expect(historyTab).toHaveAttribute('data-tour-element', `${pattern}-history-tab`); + expect(latestCheckTab).not.toHaveAttribute('data-tour-element', `${pattern}-history-tab`); + await waitFor(() => + expect(historyTab.closest('[data-test-subj="historicalResultsTour"]')).toBeInTheDocument() + ); + expect( + screen.getByRole('dialog', { name: 'Introducing data quality history' }) + ).toBeInTheDocument(); + }); + + describe('when the tour close button is clicked', () => { + it('should invoke the dismiss tour callback', async () => { + const onDismissTour = jest.fn(); + render( + <TestExternalProviders> + <TestDataQualityProviders> + <TestHistoricalResultsProvider> + <IndexCheckFlyout + ilmExplain={mockIlmExplain} + indexName="auditbeat-custom-index-1" + onClose={jest.fn()} + pattern="auditbeat-*" + patternRollup={auditbeatWithAllResults} + stats={mockStats} + initialSelectedTabId="latest_check" + onDismissTour={onDismissTour} + isTourActive={true} + /> + </TestHistoricalResultsProvider> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const dialogWrapper = await screen.findByRole('dialog', { + name: 'Introducing data quality history', + }); + + const closeButton = within(dialogWrapper).getByRole('button', { name: 'Close' }); + await userEvent.click(closeButton); + + expect(onDismissTour).toHaveBeenCalled(); + }); + }); + + describe('when the tour TryIt button is clicked', () => { + it('should switch to history tab and invoke onDismissTour', async () => { + const onDismissTour = jest.fn(); + render( + <TestExternalProviders> + <TestDataQualityProviders> + <TestHistoricalResultsProvider> + <IndexCheckFlyout + ilmExplain={mockIlmExplain} + indexName="auditbeat-custom-index-1" + onClose={jest.fn()} + pattern="auditbeat-*" + patternRollup={auditbeatWithAllResults} + stats={mockStats} + initialSelectedTabId="latest_check" + onDismissTour={onDismissTour} + isTourActive={true} + /> + </TestHistoricalResultsProvider> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const dialogWrapper = await screen.findByRole('dialog', { + name: 'Introducing data quality history', + }); + + const tryItButton = within(dialogWrapper).getByRole('button', { name: 'Try it' }); + await userEvent.click(tryItButton); + + expect(onDismissTour).toHaveBeenCalled(); + expect(screen.getByRole('tab', { name: 'History' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + + expect(onDismissTour).toHaveBeenCalled(); + }); + }); + + describe('when manually switching to history tab', () => { + it('should invoke onDismissTour', async () => { + const onDismissTour = jest.fn(); + render( + <TestExternalProviders> + <TestDataQualityProviders> + <TestHistoricalResultsProvider> + <IndexCheckFlyout + ilmExplain={mockIlmExplain} + indexName="auditbeat-custom-index-1" + onClose={jest.fn()} + pattern="auditbeat-*" + patternRollup={auditbeatWithAllResults} + stats={mockStats} + initialSelectedTabId="latest_check" + onDismissTour={onDismissTour} + isTourActive={true} + /> + </TestHistoricalResultsProvider> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + const historyTab = screen.getByRole('tab', { name: 'History' }); + await userEvent.click(historyTab); + + expect(onDismissTour).toHaveBeenCalled(); + }); + }); + }); + + describe('when not isTourActive', () => { + it('should not render the tour guide', async () => { + render( + <TestExternalProviders> + <TestDataQualityProviders> + <TestHistoricalResultsProvider> + <IndexCheckFlyout + ilmExplain={mockIlmExplain} + indexName="auditbeat-custom-index-1" + onClose={jest.fn()} + pattern="auditbeat-*" + patternRollup={auditbeatWithAllResults} + stats={mockStats} + initialSelectedTabId="latest_check" + onDismissTour={jest.fn()} + isTourActive={false} + /> + </TestHistoricalResultsProvider> + </TestDataQualityProviders> + </TestExternalProviders> + ); + + await waitFor(() => + expect(screen.queryByTestId('historicalResultsTour')).not.toBeInTheDocument() + ); + + expect( + screen.queryByRole('dialog', { name: 'Introducing data quality history' }) + ).not.toBeInTheDocument(); + }); + }); + }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx index f298af704307d..b6dcf850d15b0 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx @@ -36,8 +36,13 @@ import { HistoricalResults } from './historical_results'; import { useHistoricalResultsContext } from '../contexts/historical_results_context'; import { getFormattedCheckTime } from './utils/get_formatted_check_time'; import { CHECK_NOW } from '../translations'; -import { HISTORY_TAB_ID, LATEST_CHECK_TAB_ID } from '../constants'; +import { + HISTORICAL_RESULTS_TOUR_SELECTOR_KEY, + HISTORY_TAB_ID, + LATEST_CHECK_TAB_ID, +} from '../constants'; import { IndexCheckFlyoutTabId } from './types'; +import { HistoricalResultsTour } from '../historical_results_tour'; export interface Props { ilmExplain: Record<string, IlmExplainLifecycleLifecycleExplain> | null; @@ -47,6 +52,8 @@ export interface Props { stats: Record<string, MeteringStatsIndex> | null; onClose: () => void; initialSelectedTabId: IndexCheckFlyoutTabId; + onDismissTour: () => void; + isTourActive: boolean; } const tabs = [ @@ -68,6 +75,8 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ patternRollup, stats, onClose, + onDismissTour, + isTourActive, }) => { const didSwitchToLatestTabOnceRef = useRef(false); const { fetchHistoricalResults } = useHistoricalResultsContext(); @@ -90,12 +99,15 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ const handleTabClick = useCallback( (tabId: IndexCheckFlyoutTabId) => { + setSelectedTabId(tabId); if (tabId === HISTORY_TAB_ID) { + if (isTourActive) { + onDismissTour(); + } fetchHistoricalResults({ abortController: fetchHistoricalResultsAbortControllerRef.current, indexName, }); - setSelectedTabId(tabId); } if (tabId === LATEST_CHECK_TAB_ID) { @@ -110,7 +122,6 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ formatNumber, }); } - setSelectedTabId(tabId); } }, [ @@ -122,6 +133,8 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ formatNumber, httpFetch, indexName, + isTourActive, + onDismissTour, pattern, ] ); @@ -149,6 +162,10 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ selectedTabId, ]); + const handleSelectHistoryTab = useCallback(() => { + handleTabClick(HISTORY_TAB_ID); + }, [handleTabClick]); + const renderTabs = useMemo( () => tabs.map((tab, index) => { @@ -157,12 +174,15 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ onClick={() => handleTabClick(tab.id)} isSelected={tab.id === selectedTabId} key={index} + {...(tab.id === HISTORY_TAB_ID && { + [HISTORICAL_RESULTS_TOUR_SELECTOR_KEY]: `${pattern}-history-tab`, + })} > {tab.name} </EuiTab> ); }), - [handleTabClick, selectedTabId] + [handleTabClick, pattern, selectedTabId] ); return ( @@ -195,12 +215,20 @@ export const IndexCheckFlyoutComponent: React.FC<Props> = ({ </EuiFlyoutHeader> <EuiFlyoutBody> {selectedTabId === LATEST_CHECK_TAB_ID ? ( - <LatestResults - indexName={indexName} - stats={stats} - ilmExplain={ilmExplain} - patternRollup={patternRollup} - /> + <> + <LatestResults + indexName={indexName} + stats={stats} + ilmExplain={ilmExplain} + patternRollup={patternRollup} + /> + <HistoricalResultsTour + anchorSelectorValue={`${pattern}-history-tab`} + onTryIt={handleSelectHistoryTab} + isOpen={isTourActive} + onDismissTour={onDismissTour} + /> + </> ) : ( <HistoricalResults indexName={indexName} /> )} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx index fa574362e7d9b..02298a5b7dd94 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx @@ -30,6 +30,7 @@ export interface Props { pattern: string; onCheckNowAction: (indexName: string) => void; onViewHistoryAction: (indexName: string) => void; + firstIndexName?: string; }) => Array<EuiBasicTableColumn<IndexSummaryTableItem>>; items: IndexSummaryTableItem[]; pageIndex: number; @@ -66,6 +67,7 @@ const SummaryTableComponent: React.FC<Props> = ({ pattern, onCheckNowAction, onViewHistoryAction, + firstIndexName: items[0]?.indexName, }), [ getTableColumns, @@ -75,6 +77,7 @@ const SummaryTableComponent: React.FC<Props> = ({ pattern, onCheckNowAction, onViewHistoryAction, + items, ] ); const getItemId = useCallback((item: IndexSummaryTableItem) => item.indexName, []); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.test.tsx index eda93c45f3b4f..bffd0c7fb91de 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.test.tsx @@ -197,6 +197,60 @@ describe('helpers', () => { expect(onViewHistoryAction).toBeCalledWith(indexSummaryTableItem.indexName); }); + + test('adds data-tour-element attribute to the first view history button', () => { + const pattern = 'auditbeat-*'; + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + isILMAvailable, + pattern, + onCheckNowAction: jest.fn(), + onViewHistoryAction: jest.fn(), + firstIndexName: indexName, + }); + + const expandActionRender = ( + (columns[0] as EuiTableActionsColumnType<IndexSummaryTableItem>) + .actions[1] as CustomItemAction<IndexSummaryTableItem> + ).render; + + render( + <TestExternalProviders> + {expandActionRender != null && expandActionRender(indexSummaryTableItem, true)} + </TestExternalProviders> + ); + + const button = screen.getByLabelText(VIEW_HISTORY); + expect(button).toHaveAttribute('data-tour-element', pattern); + }); + + test('doesn`t add data-tour-element attribute to non-first view history buttons', () => { + const pattern = 'auditbeat-*'; + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + isILMAvailable, + pattern, + onCheckNowAction: jest.fn(), + onViewHistoryAction: jest.fn(), + firstIndexName: 'another-index', + }); + + const expandActionRender = ( + (columns[0] as EuiTableActionsColumnType<IndexSummaryTableItem>) + .actions[1] as CustomItemAction<IndexSummaryTableItem> + ).render; + + render( + <TestExternalProviders> + {expandActionRender != null && expandActionRender(indexSummaryTableItem, true)} + </TestExternalProviders> + ); + + const button = screen.getByLabelText(VIEW_HISTORY); + expect(button).not.toHaveAttribute('data-tour-element'); + }); }); describe('incompatible render()', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.tsx index c930d47babc2e..832ba71d26af8 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/utils/columns.tsx @@ -37,6 +37,7 @@ import { IndexResultBadge } from '../../index_result_badge'; import { Stat } from '../../../../../stat'; import { getIndexResultToolTip } from '../../utils/get_index_result_tooltip'; import { CHECK_NOW } from '../../translations'; +import { HISTORICAL_RESULTS_TOUR_SELECTOR_KEY } from '../../constants'; const ProgressContainer = styled.div` width: 150px; @@ -102,6 +103,7 @@ export const getSummaryTableColumns = ({ pattern, onCheckNowAction, onViewHistoryAction, + firstIndexName, }: { formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; @@ -109,6 +111,7 @@ export const getSummaryTableColumns = ({ pattern: string; onCheckNowAction: (indexName: string) => void; onViewHistoryAction: (indexName: string) => void; + firstIndexName?: string; }): Array<EuiBasicTableColumn<IndexSummaryTableItem>> => [ { name: i18n.ACTIONS, @@ -132,12 +135,16 @@ export const getSummaryTableColumns = ({ { name: i18n.VIEW_HISTORY, render: (item) => { + const isFirstIndexName = firstIndexName === item.indexName; return ( <EuiToolTip content={i18n.VIEW_HISTORY}> <EuiButtonIcon iconType="clockCounter" aria-label={i18n.VIEW_HISTORY} onClick={() => onViewHistoryAction(item.indexName)} + {...(isFirstIndexName && { + [HISTORICAL_RESULTS_TOUR_SELECTOR_KEY]: pattern, + })} /> </EuiToolTip> ); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts index 6f3c7b008a5af..9d0e09ef57d96 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts @@ -166,3 +166,21 @@ export const auditbeatWithAllResults: PatternRollup = { }, }, }; + +export const emptyAuditbeatPatternRollup: PatternRollup = { + docsCount: 0, + error: null, + ilmExplain: {}, + ilmExplainPhaseCounts: { + hot: 0, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 0, + }, + indices: 0, + pattern: 'auditbeat-*', + results: {}, + sizeInBytes: 0, + stats: {}, +}; From dd25bf8807c3ff3982d455f070b6e6c65233662d Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Wed, 16 Oct 2024 01:40:19 +0200 Subject: [PATCH 74/84] Skip scheduling actions for the alerts without scheduledActions (#195948) Resolves: #190258 As a result of #190258, we have found out that the odd behaviour happens when an existing alert is pushed above the max alerts limit by a new alert. Scenario: 1. The rule type detects 4 alerts (`alert-1`, `alert-2`, `alert-3`, `alert-4`), But reports only the first 3 as the max alerts limit is 3. 2. `alert-2` becomes recovered, therefore the rule type reports 3 active (`alert-1`, `alert-3`, `alert-4`), 1 recovered (`alert-2`) alert. 3. Alerts `alert-1`, `alert-3`, `alert-4` are saved in the task state. 4. `alert-2` becomes active again (the others are still active) 5. Rule type reports 3 active alerts (`alert-1`, `alert-2`, `alert-3`) 6. As a result, the action scheduler tries to schedule actions for `alert-1`, `alert-3`, `alert-4` as they are the existing alerts. But, since the rule type didn't report the `alert-4` it has no scheduled actions, therefore the action scheduler assumes it is recovered and tries to schedule a recovery action. This PR changes the actionScheduler to handle active and recovered alerts separately. With this change, no action would be scheduled for an alert from previous run (exists in the task state) and isn't reported by the ruleTypeExecutor due to max-alerts-limit but it would be kept in the task state. --- .../alerting/server/alerts_client/types.ts | 7 +- .../action_scheduler/action_scheduler.test.ts | 298 +++++++++++++----- .../action_scheduler/action_scheduler.ts | 16 +- .../lib/get_summarized_alerts.ts | 2 +- .../per_alert_action_scheduler.test.ts | 99 ++++-- .../schedulers/per_alert_action_scheduler.ts | 198 +++++++----- .../summary_action_scheduler.test.ts | 62 +++- .../schedulers/summary_action_scheduler.ts | 4 +- .../system_action_scheduler.test.ts | 14 +- .../task_runner/action_scheduler/types.ts | 30 +- .../server/task_runner/task_runner.test.ts | 4 +- .../server/task_runner/task_runner.ts | 4 +- 12 files changed, 542 insertions(+), 196 deletions(-) diff --git a/x-pack/plugins/alerting/server/alerts_client/types.ts b/x-pack/plugins/alerting/server/alerts_client/types.ts index d043f41e1e955..f3c4a85fa1b71 100644 --- a/x-pack/plugins/alerting/server/alerts_client/types.ts +++ b/x-pack/plugins/alerting/server/alerts_client/types.ts @@ -77,8 +77,11 @@ export interface IAlertsClient< processAlerts(opts: ProcessAlertsOpts): void; logAlerts(opts: LogAlertsOpts): void; getProcessedAlerts( - type: 'new' | 'active' | 'activeCurrent' | 'recovered' | 'recoveredCurrent' - ): Record<string, LegacyAlert<State, Context, ActionGroupIds | RecoveryActionGroupId>>; + type: 'new' | 'active' | 'activeCurrent' + ): Record<string, LegacyAlert<State, Context, ActionGroupIds>> | {}; + getProcessedAlerts( + type: 'recovered' | 'recoveredCurrent' + ): Record<string, LegacyAlert<State, Context, RecoveryActionGroupId>> | {}; persistAlerts(): Promise<{ alertIds: string[]; maintenanceWindowIds: string[] } | null>; isTrackedAlert(id: string): boolean; getSummarizedAlerts?(params: GetSummarizedAlertsParams): Promise<SummarizedAlerts>; diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.test.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.test.ts index b6f250b47205e..00f1a87aefd71 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.test.ts @@ -95,6 +95,7 @@ describe('Action Scheduler', () => { ); ruleRunMetricsStore = new RuleRunMetricsStore(); actionsClient.bulkEnqueueExecution.mockResolvedValue(defaultExecutionResponse); + alertsClient.getProcessedAlerts.mockReturnValue({}); }); beforeAll(() => { clock = sinon.useFakeTimers(); @@ -104,7 +105,7 @@ describe('Action Scheduler', () => { test('schedules execution per selected action', async () => { const alerts = generateAlert({ id: 1 }); const actionScheduler = new ActionScheduler(getSchedulerContext()); - await actionScheduler.run(alerts); + await actionScheduler.run({ activeCurrentAlerts: alerts, recoveredCurrentAlerts: {} }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); @@ -204,7 +205,10 @@ describe('Action Scheduler', () => { }) ); - await actionScheduler.run(generateAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); @@ -269,7 +273,10 @@ describe('Action Scheduler', () => { }) ); - await actionScheduler.run(generateAlert({ id: 2 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2 }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2); @@ -281,7 +288,10 @@ describe('Action Scheduler', () => { ruleRunMetricsStore, }); - await actionSchedulerForPreconfiguredAction.run(generateAlert({ id: 2 })); + await actionSchedulerForPreconfiguredAction.run({ + activeCurrentAlerts: generateAlert({ id: 2 }), + recoveredCurrentAlerts: {}, + }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); }); @@ -321,7 +331,10 @@ describe('Action Scheduler', () => { ); try { - await actionScheduler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }), + recoveredCurrentAlerts: {}, + }); } catch (err) { expect(getErrorSource(err)).toBe(TaskErrorSource.USER); } @@ -329,7 +342,10 @@ describe('Action Scheduler', () => { test('limits actionsPlugin.execute per action group', async () => { const actionScheduler = new ActionScheduler(getSchedulerContext()); - await actionScheduler.run(generateAlert({ id: 2, group: 'other-group' })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, group: 'other-group' }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0); expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); @@ -337,7 +353,10 @@ describe('Action Scheduler', () => { test('context attribute gets parameterized', async () => { const actionScheduler = new ActionScheduler(getSchedulerContext()); - await actionScheduler.run(generateAlert({ id: 2, context: { value: 'context-val' } })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, context: { value: 'context-val' } }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); @@ -381,7 +400,10 @@ describe('Action Scheduler', () => { test('state attribute gets parameterized', async () => { const actionScheduler = new ActionScheduler(getSchedulerContext()); - await actionScheduler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }), + recoveredCurrentAlerts: {}, + }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -423,9 +445,13 @@ describe('Action Scheduler', () => { test(`logs an error when action group isn't part of actionGroups available for the ruleType`, async () => { const actionScheduler = new ActionScheduler(getSchedulerContext()); - await actionScheduler.run( - generateAlert({ id: 2, group: 'invalid-group' as 'default' | 'other-group' }) - ); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ + id: 2, + group: 'invalid-group' as 'default' | 'other-group', + }), + recoveredCurrentAlerts: {}, + }); expect(defaultSchedulerContext.logger.error).toHaveBeenCalledWith( 'Invalid action group "invalid-group" for rule "test".' @@ -503,7 +529,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(3); @@ -604,7 +633,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(4); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(5); @@ -688,7 +720,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }), + recoveredCurrentAlerts: {}, + }); expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(3); @@ -722,7 +757,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateRecoveredAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: generateRecoveredAlert({ id: 1 }), + }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` @@ -787,7 +825,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateRecoveredAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: generateRecoveredAlert({ id: 1 }), + }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0); expect(defaultSchedulerContext.logger.debug).nthCalledWith( @@ -807,7 +848,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); clock.tick(30000); @@ -837,12 +881,13 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run( - generateAlert({ + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1, throttledActions: { '111-111': { date: new Date(DATE_1970).toISOString() } }, - }) - ); + }), + recoveredCurrentAlerts: {}, + }); clock.tick(30000); @@ -872,7 +917,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 1, lastScheduledActionsGroup: 'recovered' })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1, lastScheduledActionsGroup: 'recovered' }), + recoveredCurrentAlerts: {}, + }); clock.tick(30000); @@ -890,7 +938,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0); expect(defaultSchedulerContext.logger.debug).nthCalledWith( @@ -945,7 +996,10 @@ describe('Action Scheduler', () => { }) ); - await actionScheduler.run(generateAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ executionUuid: '5f6aa57d-3e22-484e-bae8-cbed868f4d28', @@ -1026,7 +1080,10 @@ describe('Action Scheduler', () => { }) ); - await actionScheduler.run({}); + await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: {}, + }); expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); expect(alertingEventLogger.logAction).not.toHaveBeenCalled(); @@ -1078,7 +1135,10 @@ describe('Action Scheduler', () => { }) ); - const result = await actionScheduler.run({}); + const result = await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: {}, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ start: new Date('1969-12-31T00:01:30.000Z'), @@ -1174,7 +1234,10 @@ describe('Action Scheduler', () => { }) ); - await actionScheduler.run({}); + await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: {}, + }); expect(defaultSchedulerContext.logger.debug).toHaveBeenCalledTimes(1); expect(defaultSchedulerContext.logger.debug).toHaveBeenCalledWith( "skipping scheduling the action 'testActionTypeId:1', summary action is still being throttled" @@ -1236,7 +1299,10 @@ describe('Action Scheduler', () => { }) ); - const result = await actionScheduler.run({}); + const result = await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: {}, + }); expect(result).toEqual({ throttledSummaryActions: { '111-111': { @@ -1271,7 +1337,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateAlert({ id: 2 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 2 }), + recoveredCurrentAlerts: {}, + }); expect(defaultSchedulerContext.logger.error).toHaveBeenCalledWith( 'Skipping action "1" for rule "1" because the rule type "Test" does not support alert-as-data.' @@ -1332,7 +1401,10 @@ describe('Action Scheduler', () => { }, }) ); - await actionScheduler.run(generateRecoveredAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: {}, + recoveredCurrentAlerts: generateRecoveredAlert({ id: 1 }), + }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` @@ -1455,8 +1527,11 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1 }), - ...generateAlert({ id: 2 }), + activeCurrentAlerts: { + ...generateAlert({ id: 1 }), + ...generateAlert({ id: 2 }), + }, + recoveredCurrentAlerts: {}, }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -1529,8 +1604,11 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1 }), - ...generateAlert({ id: 2 }), + activeCurrentAlerts: { + ...generateAlert({ id: 1 }), + ...generateAlert({ id: 2 }), + }, + recoveredCurrentAlerts: {}, }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -1597,9 +1675,12 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1 }), - ...generateAlert({ id: 2 }), - ...generateAlert({ id: 3 }), + activeCurrentAlerts: { + ...generateAlert({ id: 1 }), + ...generateAlert({ id: 2 }), + ...generateAlert({ id: 3 }), + }, + recoveredCurrentAlerts: {}, }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -1706,9 +1787,12 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }), - ...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }), - ...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }), + activeCurrentAlerts: { + ...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }), + ...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }), + ...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }), + }, + recoveredCurrentAlerts: {}, }); expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); @@ -1755,9 +1839,12 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }), - ...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }), - ...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }), + activeCurrentAlerts: { + ...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }), + ...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }), + ...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }), + }, + recoveredCurrentAlerts: {}, }); expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); @@ -1773,9 +1860,12 @@ describe('Action Scheduler', () => { const actionScheduler = new ActionScheduler(getSchedulerContext()); await actionScheduler.run({ - ...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }), - ...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }), - ...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }), + activeCurrentAlerts: { + ...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }), + ...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }), + ...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }), + }, + recoveredCurrentAlerts: {}, }); expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); @@ -1813,9 +1903,24 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), - ...generateAlert({ id: 2, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), - ...generateAlert({ id: 3, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + activeCurrentAlerts: { + ...generateAlert({ + id: 1, + pendingRecoveredCount: 1, + lastScheduledActionsGroup: 'recovered', + }), + ...generateAlert({ + id: 2, + pendingRecoveredCount: 1, + lastScheduledActionsGroup: 'recovered', + }), + ...generateAlert({ + id: 3, + pendingRecoveredCount: 1, + lastScheduledActionsGroup: 'recovered', + }), + }, + recoveredCurrentAlerts: {}, }); expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); @@ -1842,9 +1947,24 @@ describe('Action Scheduler', () => { ); await actionScheduler.run({ - ...generateAlert({ id: 1, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), - ...generateAlert({ id: 2, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), - ...generateAlert({ id: 3, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + activeCurrentAlerts: { + ...generateAlert({ + id: 1, + pendingRecoveredCount: 1, + lastScheduledActionsGroup: 'recovered', + }), + ...generateAlert({ + id: 2, + pendingRecoveredCount: 1, + lastScheduledActionsGroup: 'recovered', + }), + ...generateAlert({ + id: 3, + pendingRecoveredCount: 1, + lastScheduledActionsGroup: 'recovered', + }), + }, + recoveredCurrentAlerts: {}, }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); @@ -1991,7 +2111,11 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); + + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -2024,7 +2148,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0][0].actionParams).toEqual({ val: 'rule url: http://localhost:12345/kbn/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1', @@ -2064,8 +2191,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2100,8 +2229,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2133,8 +2264,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2166,8 +2299,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2196,8 +2331,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2226,8 +2363,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2259,8 +2398,10 @@ describe('Action Scheduler', () => { }; const actionScheduler = new ActionScheduler(getSchedulerContext(execParams)); - await actionScheduler.run(generateAlert({ id: 1 })); - + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -2328,8 +2469,10 @@ describe('Action Scheduler', () => { const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams)); - const res = await actionScheduler.run(generateAlert({ id: 1 })); - + const res = await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); /** * Verifies that system actions are not throttled */ @@ -2451,7 +2594,10 @@ describe('Action Scheduler', () => { const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams)); - const res = await actionScheduler.run(generateAlert({ id: 1 })); + const res = await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); /** * Verifies that system actions are not throttled @@ -2508,7 +2654,10 @@ describe('Action Scheduler', () => { const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams)); - const res = await actionScheduler.run(generateAlert({ id: 1 })); + const res = await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(res).toEqual({ throttledSummaryActions: {} }); expect(buildActionParams).not.toHaveBeenCalled(); @@ -2547,7 +2696,10 @@ describe('Action Scheduler', () => { const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams)); - await actionScheduler.run(generateAlert({ id: 1 })); + await actionScheduler.run({ + activeCurrentAlerts: generateAlert({ id: 1 }), + recoveredCurrentAlerts: {}, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(buildActionParams).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.ts index 44822657ba86f..fa16cfcabb094 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/action_scheduler.ts @@ -74,9 +74,13 @@ export class ActionScheduler< this.schedulers.sort((a, b) => a.priority - b.priority); } - public async run( - alerts: Record<string, Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>> - ): Promise<RunResult> { + public async run({ + activeCurrentAlerts, + recoveredCurrentAlerts, + }: { + activeCurrentAlerts?: Record<string, Alert<State, Context, ActionGroupIds>>; + recoveredCurrentAlerts?: Record<string, Alert<State, Context, RecoveryActionGroupId>>; + }): Promise<RunResult> { const throttledSummaryActions: ThrottledActions = getSummaryActionsFromTaskState({ actions: this.context.rule.actions, summaryActions: this.context.taskInstance.state?.summaryActions, @@ -85,7 +89,11 @@ export class ActionScheduler< const allActionsToScheduleResult: ActionsToSchedule[] = []; for (const scheduler of this.schedulers) { allActionsToScheduleResult.push( - ...(await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions })) + ...(await scheduler.getActionsToSchedule({ + activeCurrentAlerts, + recoveredCurrentAlerts, + throttledSummaryActions, + })) ); } diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/lib/get_summarized_alerts.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/lib/get_summarized_alerts.ts index 00e155856d946..56d9c08c8b98f 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/lib/get_summarized_alerts.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/lib/get_summarized_alerts.ts @@ -56,7 +56,7 @@ export const getSummarizedAlerts = async < * yet (the update call uses refresh: false). So we need to rely on the in * memory alerts to do this. */ - const newAlertsInMemory = Object.values(alertsClient.getProcessedAlerts('new') || {}) || []; + const newAlertsInMemory = Object.values(alertsClient.getProcessedAlerts('new')); const newAlertsWithMaintenanceWindowIds = newAlertsInMemory.reduce<string[]>((result, alert) => { if (alert.getMaintenanceWindowIds().length > 0) { diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.test.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.test.ts index 99a693133a2a6..62e501f6963af 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.test.ts @@ -213,7 +213,9 @@ describe('Per-Alert Action Scheduler', () => { test('should create action to schedule for each alert and each action', async () => { // 2 per-alert actions * 2 alerts = 4 actions to schedule const scheduler = new PerAlertActionScheduler(getSchedulerContext()); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(logger.debug).not.toHaveBeenCalled(); @@ -243,7 +245,9 @@ describe('Per-Alert Action Scheduler', () => { maintenanceWindowIds: ['mw-1'], }); const alertsWithMaintenanceWindow = { ...newAlertWithMaintenanceWindow, ...newAlert2 }; - const results = await scheduler.getActionsToSchedule({ alerts: alertsWithMaintenanceWindow }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alertsWithMaintenanceWindow, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(logger.debug).toHaveBeenCalledTimes(2); @@ -281,7 +285,7 @@ describe('Per-Alert Action Scheduler', () => { }); const alertsWithInvalidActionGroup = { ...newAlertInvalidActionGroup, ...newAlert2 }; const results = await scheduler.getActionsToSchedule({ - alerts: alertsWithInvalidActionGroup, + activeCurrentAlerts: alertsWithInvalidActionGroup, }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); @@ -309,6 +313,35 @@ describe('Per-Alert Action Scheduler', () => { ]); }); + test('should skip creating actions to schedule when alert has no scheduled actions', async () => { + // 2 per-alert actions * 2 alerts = 4 actions to schedule + // but alert 1 has has no scheduled actions, so only actions for alert 2 should be scheduled + const scheduler = new PerAlertActionScheduler(getSchedulerContext()); + const newAlertInvalidActionGroup = generateAlert({ + id: 1, + scheduleActions: false, + }); + const alertsWithInvalidActionGroup = { ...newAlertInvalidActionGroup, ...newAlert2 }; + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alertsWithInvalidActionGroup, + }); + + expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); + + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toEqual(2); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toEqual(2); + expect(ruleRunMetricsStore.getStatusByConnectorType('test')).toEqual({ + numberOfGeneratedActions: 2, + numberOfTriggeredActions: 2, + }); + + expect(results).toHaveLength(2); + expect(results).toEqual([ + getResult('action-1', '2', '111-111'), + getResult('action-2', '2', '222-222'), + ]); + }); + test('should skip creating actions to schedule when alert has pending recovered count greater than 0 and notifyWhen is onActiveAlert', async () => { // 2 per-alert actions * 2 alerts = 4 actions to schedule // but alert 1 has a pending recovered count > 0 & notifyWhen is onActiveAlert, so only actions for alert 2 should be scheduled @@ -322,7 +355,7 @@ describe('Per-Alert Action Scheduler', () => { ...newAlert2, }; const results = await scheduler.getActionsToSchedule({ - alerts: alertsWithPendingRecoveredCount, + activeCurrentAlerts: alertsWithPendingRecoveredCount, }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); @@ -368,7 +401,7 @@ describe('Per-Alert Action Scheduler', () => { ...newAlert2, }; const results = await scheduler.getActionsToSchedule({ - alerts: alertsWithPendingRecoveredCount, + activeCurrentAlerts: alertsWithPendingRecoveredCount, }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); @@ -394,7 +427,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, mutedInstanceIds: ['2'] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(logger.debug).toHaveBeenCalledTimes(1); @@ -453,7 +488,9 @@ describe('Per-Alert Action Scheduler', () => { rule: { ...rule, actions: [rule.actions[0], onActionGroupChangeAction] }, }); - const results = await scheduler.getActionsToSchedule({ alerts: alertsWithOngoingAlert }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alertsWithOngoingAlert, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(logger.debug).toHaveBeenCalledTimes(1); @@ -508,7 +545,9 @@ describe('Per-Alert Action Scheduler', () => { rule: { ...rule, actions: [rule.actions[0], onThrottleIntervalAction] }, }); - const results = await scheduler.getActionsToSchedule({ alerts: alertsWithOngoingAlert }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alertsWithOngoingAlert, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(logger.debug).toHaveBeenCalledTimes(1); @@ -563,7 +602,9 @@ describe('Per-Alert Action Scheduler', () => { rule: { ...rule, actions: [rule.actions[0], onThrottleIntervalAction] }, }); - const results = await scheduler.getActionsToSchedule({ alerts: alertsWithOngoingAlert }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alertsWithOngoingAlert, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); expect(logger.debug).not.toHaveBeenCalled(); @@ -620,7 +661,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, actions: [rule.actions[0], actionWithUseAlertDataForTemplate] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -679,7 +722,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, actions: [rule.actions[0], actionWithUseAlertDataForTemplate] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -739,7 +784,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -799,7 +846,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -860,7 +909,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -919,7 +970,9 @@ describe('Per-Alert Action Scheduler', () => { ...getSchedulerContext(), rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -960,7 +1013,9 @@ describe('Per-Alert Action Scheduler', () => { }, }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); @@ -996,7 +1051,9 @@ describe('Per-Alert Action Scheduler', () => { }, }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); @@ -1029,7 +1086,9 @@ describe('Per-Alert Action Scheduler', () => { expect(alert.getLastScheduledActions()).toBeUndefined(); expect(alert.hasScheduledActions()).toBe(true); - await scheduler.getActionsToSchedule({ alerts: { '1': alert } }); + await scheduler.getActionsToSchedule({ + activeCurrentAlerts: { '1': alert }, + }); expect(alert.getLastScheduledActions()).toEqual({ date: '1970-01-01T00:00:00.000Z', @@ -1066,7 +1125,9 @@ describe('Per-Alert Action Scheduler', () => { rule: { ...rule, actions: [onThrottleIntervalAction] }, }); - await scheduler.getActionsToSchedule({ alerts: { '1': alert } }); + await scheduler.getActionsToSchedule({ + activeCurrentAlerts: { '1': alert }, + }); expect(alert.getLastScheduledActions()).toEqual({ date: '1970-01-01T00:00:00.000Z', diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.ts index b35d86dff0105..28b35d885b3d2 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/per_alert_action_scheduler.ts @@ -25,8 +25,12 @@ import { import { ActionSchedulerOptions, ActionsToSchedule, + AddSummarizedAlertsOpts, GetActionsToScheduleOpts, + HelperOpts, IActionScheduler, + IsExecutableActiveAlertOpts, + IsExecutableAlertOpts, } from '../types'; import { TransformActionParamsOptions, transformActionParams } from '../../transform_action_params'; import { injectActionParams } from '../../inject_action_params'; @@ -96,7 +100,8 @@ export class PerAlertActionScheduler< } public async getActionsToSchedule({ - alerts, + activeCurrentAlerts, + recoveredCurrentAlerts, }: GetActionsToScheduleOpts<State, Context, ActionGroupIds, RecoveryActionGroupId>): Promise< ActionsToSchedule[] > { @@ -106,7 +111,9 @@ export class PerAlertActionScheduler< }> = []; const results: ActionsToSchedule[] = []; - const alertsArray = Object.entries(alerts); + const activeCurrentAlertsArray = Object.values(activeCurrentAlerts || {}); + const recoveredCurrentAlertsArray = Object.values(recoveredCurrentAlerts || {}); + for (const action of this.actions) { let summarizedAlerts = null; @@ -133,61 +140,26 @@ export class PerAlertActionScheduler< logNumberOfFilteredAlerts({ logger: this.context.logger, - numberOfAlerts: Object.entries(alerts).length, + numberOfAlerts: activeCurrentAlertsArray.length + recoveredCurrentAlertsArray.length, numberOfSummarizedAlerts: summarizedAlerts.all.count, action, }); } - for (const [alertId, alert] of alertsArray) { - const alertMaintenanceWindowIds = alert.getMaintenanceWindowIds(); - if (alertMaintenanceWindowIds.length !== 0) { - this.context.logger.debug( - `no scheduling of summary actions "${action.id}" for rule "${ - this.context.rule.id - }": has active maintenance windows ${alertMaintenanceWindowIds.join(', ')}.` - ); - continue; - } - - if (alert.isFilteredOut(summarizedAlerts)) { - continue; - } - - const actionGroup = - alert.getScheduledActionOptions()?.actionGroup || - this.context.ruleType.recoveryActionGroup.id; - - if (!this.ruleTypeActionGroups!.has(actionGroup)) { - this.context.logger.error( - `Invalid action group "${actionGroup}" for rule "${this.context.ruleType.id}".` - ); - continue; - } - - // only actions with notifyWhen set to "on status change" should return - // notifications for flapping pending recovered alerts + for (const alert of activeCurrentAlertsArray) { if ( - alert.getPendingRecoveredCount() > 0 && - action?.frequency?.notifyWhen !== RuleNotifyWhen.CHANGE + this.isExecutableAlert({ alert, action, summarizedAlerts }) && + this.isExecutableActiveAlert({ alert, action }) ) { - continue; - } - - if (summarizedAlerts) { - const alertAsData = summarizedAlerts.all.data.find( - (alertHit: AlertHit) => alertHit._id === alert.getUuid() - ); - if (alertAsData) { - alert.setAlertAsData(alertAsData); - } + this.addSummarizedAlerts({ alert, summarizedAlerts }); + executables.push({ action, alert }); } + } - if (action.group === actionGroup && !this.isAlertMuted(alertId)) { - if ( - this.isRecoveredAlert(action.group) || - this.isExecutableActiveAlert({ alert, action }) - ) { + if (this.isRecoveredAction(action.group)) { + for (const alert of recoveredCurrentAlertsArray) { + if (this.isExecutableAlert({ alert, action, summarizedAlerts })) { + this.addSummarizedAlerts({ alert, summarizedAlerts }); executables.push({ action, alert }); } } @@ -285,7 +257,7 @@ export class PerAlertActionScheduler< }, }); - if (!this.isRecoveredAlert(actionGroup)) { + if (!this.isRecoveredAction(actionGroup)) { if (isActionOnInterval(action)) { alert.updateLastScheduledActions( action.group as ActionGroupIds, @@ -302,30 +274,34 @@ export class PerAlertActionScheduler< return results; } - private isAlertMuted(alertId: string) { - const muted = this.mutedAlertIdsSet.has(alertId); - if (muted) { - if ( - !this.skippedAlerts[alertId] || - (this.skippedAlerts[alertId] && this.skippedAlerts[alertId].reason !== Reasons.MUTED) - ) { - this.context.logger.debug( - `skipping scheduling of actions for '${alertId}' in rule ${this.context.ruleLabel}: rule is muted` - ); - } - this.skippedAlerts[alertId] = { reason: Reasons.MUTED }; - return true; - } - return false; - } - - private isExecutableActiveAlert({ + private isExecutableAlert({ alert, action, - }: { - alert: Alert<AlertInstanceState, AlertInstanceContext, ActionGroupIds | RecoveryActionGroupId>; - action: RuleAction; - }) { + summarizedAlerts, + }: IsExecutableAlertOpts<ActionGroupIds, RecoveryActionGroupId>) { + return ( + !this.hasActiveMaintenanceWindow({ alert, action }) && + !this.isAlertMuted(alert) && + !this.hasPendingCountButNotNotifyOnChange({ alert, action }) && + !alert.isFilteredOut(summarizedAlerts) + ); + } + + private isExecutableActiveAlert({ alert, action }: IsExecutableActiveAlertOpts<ActionGroupIds>) { + if (!alert.hasScheduledActions()) { + return false; + } + + const alertsActionGroup = alert.getScheduledActionOptions()?.actionGroup; + + if (!this.isValidActionGroup(alertsActionGroup as ActionGroupIds)) { + return false; + } + + if (action.group !== alertsActionGroup) { + return false; + } + const alertId = alert.getId(); const { context: { rule, logger, ruleLabel }, @@ -369,10 +345,86 @@ export class PerAlertActionScheduler< } } - return alert.hasScheduledActions(); + return true; } - private isRecoveredAlert(actionGroup: string) { + private isRecoveredAction(actionGroup: string) { return actionGroup === this.context.ruleType.recoveryActionGroup.id; } + + private isAlertMuted( + alert: Alert<AlertInstanceState, AlertInstanceContext, ActionGroupIds | RecoveryActionGroupId> + ) { + const alertId = alert.getId(); + const muted = this.mutedAlertIdsSet.has(alertId); + if (muted) { + if ( + !this.skippedAlerts[alertId] || + (this.skippedAlerts[alertId] && this.skippedAlerts[alertId].reason !== Reasons.MUTED) + ) { + this.context.logger.debug( + `skipping scheduling of actions for '${alertId}' in rule ${this.context.ruleLabel}: rule is muted` + ); + } + this.skippedAlerts[alertId] = { reason: Reasons.MUTED }; + return true; + } + return false; + } + + private isValidActionGroup(actionGroup: ActionGroupIds | RecoveryActionGroupId) { + if (!this.ruleTypeActionGroups!.has(actionGroup)) { + this.context.logger.error( + `Invalid action group "${actionGroup}" for rule "${this.context.ruleType.id}".` + ); + return false; + } + return true; + } + + private hasActiveMaintenanceWindow({ + alert, + action, + }: HelperOpts<ActionGroupIds, RecoveryActionGroupId>) { + const alertMaintenanceWindowIds = alert.getMaintenanceWindowIds(); + if (alertMaintenanceWindowIds.length !== 0) { + this.context.logger.debug( + `no scheduling of summary actions "${action.id}" for rule "${ + this.context.rule.id + }": has active maintenance windows ${alertMaintenanceWindowIds.join(', ')}.` + ); + return true; + } + + return false; + } + + private addSummarizedAlerts({ + alert, + summarizedAlerts, + }: AddSummarizedAlertsOpts<ActionGroupIds, RecoveryActionGroupId>) { + if (summarizedAlerts) { + const alertAsData = summarizedAlerts.all.data.find( + (alertHit: AlertHit) => alertHit._id === alert.getUuid() + ); + if (alertAsData) { + alert.setAlertAsData(alertAsData); + } + } + } + + private hasPendingCountButNotNotifyOnChange({ + alert, + action, + }: HelperOpts<ActionGroupIds, RecoveryActionGroupId>) { + // only actions with notifyWhen set to "on status change" should return + // notifications for flapping pending recovered alerts + if ( + alert.getPendingRecoveredCount() > 0 && + action?.frequency?.notifyWhen !== RuleNotifyWhen.CHANGE + ) { + return true; + } + return false; + } } diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.test.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.test.ts index fc810fc4ef34c..cb19cb781ae3e 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.test.ts @@ -13,7 +13,13 @@ import { alertingEventLoggerMock } from '../../../lib/alerting_event_logger/aler import { RuleRunMetricsStore } from '../../../lib/rule_run_metrics_store'; import { mockAAD } from '../../fixtures'; import { SummaryActionScheduler } from './summary_action_scheduler'; -import { getRule, getRuleType, getDefaultSchedulerContext, generateAlert } from '../test_fixtures'; +import { + getRule, + getRuleType, + getDefaultSchedulerContext, + generateAlert, + generateRecoveredAlert, +} from '../test_fixtures'; import { RuleAction } from '@kbn/alerting-types'; import { ALERT_UUID } from '@kbn/rule-data-utils'; import { @@ -165,6 +171,7 @@ describe('Summary Action Scheduler', () => { describe('getActionsToSchedule', () => { const newAlert1 = generateAlert({ id: 1 }); const newAlert2 = generateAlert({ id: 2 }); + const recoveredAlert = generateRecoveredAlert({ id: 3 }); const alerts = { ...newAlert1, ...newAlert2 }; const summaryActionWithAlertFilter: RuleAction = { @@ -217,7 +224,10 @@ describe('Summary Action Scheduler', () => { const throttledSummaryActions = {}; const scheduler = new SummaryActionScheduler(getSchedulerContext()); - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(2); @@ -266,7 +276,10 @@ describe('Summary Action Scheduler', () => { }); const throttledSummaryActions = {}; - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); @@ -307,7 +320,10 @@ describe('Summary Action Scheduler', () => { }); const throttledSummaryActions = {}; - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({ '444-444': { date: '1970-01-01T00:00:00.000Z' } }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); @@ -340,7 +356,10 @@ describe('Summary Action Scheduler', () => { }); const throttledSummaryActions = { '444-444': { date: '1969-12-31T13:00:00.000Z' } }; - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({ '444-444': { date: '1969-12-31T13:00:00.000Z' } }); expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled(); @@ -374,7 +393,10 @@ describe('Summary Action Scheduler', () => { const scheduler = new SummaryActionScheduler(getSchedulerContext()); const throttledSummaryActions = {}; - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(2); @@ -436,7 +458,11 @@ describe('Summary Action Scheduler', () => { }); const throttledSummaryActions = {}; - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + recoveredCurrentAlerts: recoveredAlert, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); @@ -449,7 +475,7 @@ describe('Summary Action Scheduler', () => { }); expect(logger.debug).toHaveBeenCalledTimes(1); expect(logger.debug).toHaveBeenCalledWith( - `(1) alert has been filtered out for: test:333-333` + `(2) alerts have been filtered out for: test:333-333` ); expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toEqual(1); @@ -480,7 +506,10 @@ describe('Summary Action Scheduler', () => { }); const throttledSummaryActions = {}; - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions, + }); expect(throttledSummaryActions).toEqual({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); @@ -507,7 +536,10 @@ describe('Summary Action Scheduler', () => { const scheduler = new SummaryActionScheduler(getSchedulerContext()); try { - await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions: {} }); + await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions: {}, + }); } catch (err) { expect(err.message).toEqual(`no alerts for you`); expect(getErrorSource(err)).toBe(TaskErrorSource.FRAMEWORK); @@ -533,7 +565,10 @@ describe('Summary Action Scheduler', () => { }, }, }); - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions: {} }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions: {}, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(2); expect(alertsClient.getSummarizedAlerts).toHaveBeenNthCalledWith(1, { @@ -587,7 +622,10 @@ describe('Summary Action Scheduler', () => { }, }, }); - const results = await scheduler.getActionsToSchedule({ alerts, throttledSummaryActions: {} }); + const results = await scheduler.getActionsToSchedule({ + activeCurrentAlerts: alerts, + throttledSummaryActions: {}, + }); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(2); expect(alertsClient.getSummarizedAlerts).toHaveBeenNthCalledWith(1, { diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.ts index 050eea352f0d5..db53f15be2180 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/summary_action_scheduler.ts @@ -81,11 +81,13 @@ export class SummaryActionScheduler< } public async getActionsToSchedule({ - alerts, + activeCurrentAlerts, + recoveredCurrentAlerts, throttledSummaryActions, }: GetActionsToScheduleOpts<State, Context, ActionGroupIds, RecoveryActionGroupId>): Promise< ActionsToSchedule[] > { + const alerts = { ...activeCurrentAlerts, ...recoveredCurrentAlerts }; const executables: Array<{ action: RuleAction; summarizedAlerts: CombinedSummarizedAlerts; diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/system_action_scheduler.test.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/system_action_scheduler.test.ts index 28bf58a30c689..71a7584c7280b 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/system_action_scheduler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/schedulers/system_action_scheduler.test.ts @@ -160,7 +160,7 @@ describe('System Action Scheduler', () => { alertsClient.getSummarizedAlerts.mockResolvedValue(summarizedAlerts); const scheduler = new SystemActionScheduler(getSchedulerContext()); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -202,7 +202,7 @@ describe('System Action Scheduler', () => { alertsClient.getSummarizedAlerts.mockResolvedValue(summarizedAlerts); const scheduler = new SystemActionScheduler(getSchedulerContext()); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -240,7 +240,7 @@ describe('System Action Scheduler', () => { alertsClient.getSummarizedAlerts.mockResolvedValue(summarizedAlerts); const scheduler = new SystemActionScheduler(getSchedulerContext()); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ @@ -265,7 +265,7 @@ describe('System Action Scheduler', () => { const scheduler = new SystemActionScheduler(getSchedulerContext()); try { - await scheduler.getActionsToSchedule({ alerts }); + await scheduler.getActionsToSchedule({}); } catch (err) { expect(err.message).toEqual(`no alerts for you`); expect(getErrorSource(err)).toBe(TaskErrorSource.FRAMEWORK); @@ -299,7 +299,7 @@ describe('System Action Scheduler', () => { }, }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(2); expect(alertsClient.getSummarizedAlerts).toHaveBeenNthCalledWith(1, { @@ -361,7 +361,7 @@ describe('System Action Scheduler', () => { }, }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(2); expect(alertsClient.getSummarizedAlerts).toHaveBeenNthCalledWith(1, { @@ -416,7 +416,7 @@ describe('System Action Scheduler', () => { ...defaultContext, rule: { ...rule, systemActions: [differentSystemAction] }, }); - const results = await scheduler.getActionsToSchedule({ alerts }); + const results = await scheduler.getActionsToSchedule({}); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1); expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/alerting/server/task_runner/action_scheduler/types.ts b/x-pack/plugins/alerting/server/task_runner/action_scheduler/types.ts index b90ffb88d541b..02b9647f91866 100644 --- a/x-pack/plugins/alerting/server/task_runner/action_scheduler/types.ts +++ b/x-pack/plugins/alerting/server/task_runner/action_scheduler/types.ts @@ -90,7 +90,8 @@ export interface GetActionsToScheduleOpts< ActionGroupIds extends string, RecoveryActionGroupId extends string > { - alerts: Record<string, Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>>; + activeCurrentAlerts?: Record<string, Alert<State, Context, ActionGroupIds>>; + recoveredCurrentAlerts?: Record<string, Alert<State, Context, RecoveryActionGroupId>>; throttledSummaryActions?: ThrottledActions; } @@ -118,3 +119,30 @@ export interface RuleUrl { spaceIdSegment?: string; relativePath?: string; } + +export interface IsExecutableAlertOpts< + ActionGroupIds extends string, + RecoveryActionGroupId extends string +> { + alert: Alert<AlertInstanceState, AlertInstanceContext, ActionGroupIds | RecoveryActionGroupId>; + action: RuleAction; + summarizedAlerts: CombinedSummarizedAlerts | null; +} + +export interface IsExecutableActiveAlertOpts<ActionGroupIds extends string> { + alert: Alert<AlertInstanceState, AlertInstanceContext, ActionGroupIds>; + action: RuleAction; +} + +export interface HelperOpts<ActionGroupIds extends string, RecoveryActionGroupId extends string> { + alert: Alert<AlertInstanceState, AlertInstanceContext, ActionGroupIds | RecoveryActionGroupId>; + action: RuleAction; +} + +export interface AddSummarizedAlertsOpts< + ActionGroupIds extends string, + RecoveryActionGroupId extends string +> { + alert: Alert<AlertInstanceState, AlertInstanceContext, ActionGroupIds | RecoveryActionGroupId>; + summarizedAlerts: CombinedSummarizedAlerts | null; +} diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index b6e59402ba4c6..a79dfe8f59c73 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -1677,6 +1677,7 @@ describe('Task Runner', () => { return { state: {} }; }); + alertsClient.getProcessedAlerts.mockReturnValue({}); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 1, @@ -1738,7 +1739,7 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation(async () => { return { state: {} }; }); - + alertsClient.getProcessedAlerts.mockReturnValue({}); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 1, @@ -1747,6 +1748,7 @@ describe('Task Runner', () => { ongoing: { count: 0, data: [] }, recovered: { count: 0, data: [] }, }); + alertsClient.getAlertsToSerialize.mockResolvedValueOnce({ state: {}, meta: {} }); alertsService.createAlertsClient.mockImplementation(() => alertsClient); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 897937ce55a0a..89432e1822029 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -414,8 +414,8 @@ export class TaskRunner< this.countUsageOfActionExecutionAfterRuleCancellation(); } else { actionSchedulerResult = await actionScheduler.run({ - ...alertsClient.getProcessedAlerts('activeCurrent'), - ...alertsClient.getProcessedAlerts('recoveredCurrent'), + activeCurrentAlerts: alertsClient.getProcessedAlerts('activeCurrent'), + recoveredCurrentAlerts: alertsClient.getProcessedAlerts('recoveredCurrent'), }); } }) From 8cadf88c66a257c073279fa11572b089c32eb643 Mon Sep 17 00:00:00 2001 From: Jen Huang <its.jenetic@gmail.com> Date: Tue, 15 Oct 2024 16:57:32 -0700 Subject: [PATCH 75/84] [UII] Restrict agentless integrations to deployments with agentless enabled (#194885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Resolves #192486. This PR makes it so that on deployments without agentless enabled: 1. Agentless-only integrations are hidden from the browse integration UI 2. Agentless-only integrations cannot be installed via API (unless force flag is used) ⚠️ https://github.com/elastic/package-registry/issues/1238 needs to be completed for the below testing steps to work. Currently EPR does not return `deployment_modes` property which is necessary for Fleet to know which packages are agentless. ## How to test 1. Simulate agentless being available by adding the following to kibana.yml: ``` xpack.fleet.agentless.enabled: true # Simulate cloud xpack.cloud.id: "foo" xpack.cloud.base_url: "https://cloud.elastic.co" xpack.cloud.organization_url: "/account/" xpack.cloud.billing_url: "/billing/" xpack.cloud.profile_url: "/user/settings/" ``` 2. Go to `Integrations > Browse` and enable showing Beta integrations, search for `connector` and you should see the agentless integrations: Elastic Connectors, GitHub & GitHub Enterprise Server Connector, Google Drive Connector 3. Install any one of them (they all come from the same package), it should be successful 4. Uninstall them 5. Remove config changes to go back to a non-agentless deployment 6. Refresh Integrations list, the three integrations should no longer appear 7. Try installing via API, an error should appear ``` POST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2 ``` 8. Try installing via API again with force flag, it should be successful: ``` POST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2 { "force": true } ``` ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../services/agentless_policy_helper.test.ts | 287 ++++++++++++++++++ .../services/agentless_policy_helper.ts | 41 +++ .../hooks/setup_technology.ts | 14 +- .../hooks/use_package_policy_steps.tsx | 1 - .../home/hooks/use_available_packages.tsx | 38 ++- .../plugins/fleet/public/hooks/use_config.ts | 23 +- x-pack/plugins/fleet/public/hooks/use_core.ts | 7 +- .../fleet/public/mock/plugin_interfaces.ts | 2 + x-pack/plugins/fleet/public/plugin.ts | 3 +- .../plugins/fleet/server/errors/handlers.ts | 4 + x-pack/plugins/fleet/server/errors/index.ts | 1 + .../services/epm/packages/install.test.ts | 69 ++++- .../server/services/epm/packages/install.ts | 18 ++ .../fleet/server/services/utils/agentless.ts | 7 +- 14 files changed, 488 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/fleet/common/services/agentless_policy_helper.test.ts diff --git a/x-pack/plugins/fleet/common/services/agentless_policy_helper.test.ts b/x-pack/plugins/fleet/common/services/agentless_policy_helper.test.ts new file mode 100644 index 0000000000000..aed3020c9dcf1 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/agentless_policy_helper.test.ts @@ -0,0 +1,287 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RegistryPolicyTemplate } from '../types'; + +import { + isAgentlessIntegration, + getAgentlessAgentPolicyNameFromPackagePolicyName, + isOnlyAgentlessIntegration, + isOnlyAgentlessPolicyTemplate, +} from './agentless_policy_helper'; + +describe('agentless_policy_helper', () => { + describe('isAgentlessIntegration', () => { + it('should return true if packageInfo is defined and has at least one agentless integration', () => { + const packageInfo = { + policy_templates: [ + { + name: 'template1', + title: 'Template 1', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + agentless: { + enabled: true, + }, + }, + }, + { + name: 'template2', + title: 'Template 2', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + }, + }, + ] as RegistryPolicyTemplate[], + }; + + const result = isAgentlessIntegration(packageInfo); + + expect(result).toBe(true); + }); + + it('should return false if packageInfo is defined but does not have agentless integrations', () => { + const packageInfo = { + policy_templates: [ + { + name: 'template1', + title: 'Template 1', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + agentless: { + enabled: false, + }, + }, + }, + { + name: 'template2', + title: 'Template 2', + description: '', + deployment_modes: { + default: { + enabled: false, + }, + agentless: { + enabled: false, + }, + }, + }, + ] as RegistryPolicyTemplate[], + }; + + const result = isAgentlessIntegration(packageInfo); + + expect(result).toBe(false); + }); + + it('should return false if packageInfo has no policy templates', () => { + const packageInfo = { + policy_templates: [], + }; + + const result = isAgentlessIntegration(packageInfo); + + expect(result).toBe(false); + }); + + it('should return false if packageInfo is undefined', () => { + const packageInfo = undefined; + + const result = isAgentlessIntegration(packageInfo); + + expect(result).toBe(false); + }); + }); + + describe('getAgentlessAgentPolicyNameFromPackagePolicyName', () => { + it('should return the agentless agent policy name based on the package policy name', () => { + const packagePolicyName = 'example-package-policy'; + + const result = getAgentlessAgentPolicyNameFromPackagePolicyName(packagePolicyName); + + expect(result).toBe('Agentless policy for example-package-policy'); + }); + }); + + describe('isOnlyAgentlessIntegration', () => { + it('should return true if packageInfo is defined and has only agentless integration', () => { + const packageInfo = { + policy_templates: [ + { + name: 'template1', + title: 'Template 1', + description: '', + deployment_modes: { + default: { + enabled: false, + }, + agentless: { + enabled: true, + }, + }, + }, + { + name: 'template2', + title: 'Template 2', + description: '', + deployment_modes: { + agentless: { + enabled: true, + }, + }, + }, + ] as RegistryPolicyTemplate[], + }; + + const result = isOnlyAgentlessIntegration(packageInfo); + + expect(result).toBe(true); + }); + + it('should return false if packageInfo is defined but has other deployment types', () => { + const packageInfo = { + policy_templates: [ + { + name: 'template1', + title: 'Template 1', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + agentless: { + enabled: true, + }, + }, + }, + { + name: 'template2', + title: 'Template 2', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + }, + }, + ] as RegistryPolicyTemplate[], + }; + + const result = isOnlyAgentlessIntegration(packageInfo); + + expect(result).toBe(false); + }); + + it('should return false if packageInfo has no policy templates', () => { + const packageInfo = { + policy_templates: [], + }; + + const result = isOnlyAgentlessIntegration(packageInfo); + + expect(result).toBe(false); + }); + + it('should return false if packageInfo is undefined', () => { + const packageInfo = undefined; + + const result = isOnlyAgentlessIntegration(packageInfo); + + expect(result).toBe(false); + }); + }); + + describe('isOnlyAgentlessPolicyTemplate', () => { + it('should return true if the policy template is only agentless', () => { + const policyTemplate = { + name: 'template1', + title: 'Template 1', + description: '', + deployment_modes: { + default: { + enabled: false, + }, + agentless: { + enabled: true, + }, + }, + }; + const policyTemplate2 = { + name: 'template2', + title: 'Template 2', + description: '', + deployment_modes: { + agentless: { + enabled: true, + }, + }, + }; + + const result = isOnlyAgentlessPolicyTemplate(policyTemplate); + const result2 = isOnlyAgentlessPolicyTemplate(policyTemplate2); + + expect(result).toBe(true); + expect(result2).toBe(true); + }); + + it('should return false if the policy template has other deployment types', () => { + const policyTemplate = { + name: 'template1', + title: 'Template 1', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + agentless: { + enabled: true, + }, + }, + }; + const policyTemplate2 = { + name: 'template2', + title: 'Template 2', + description: '', + deployment_modes: { + default: { + enabled: true, + }, + agentless: { + enabled: false, + }, + }, + }; + + const result = isOnlyAgentlessPolicyTemplate(policyTemplate); + const result2 = isOnlyAgentlessPolicyTemplate(policyTemplate2); + + expect(result).toBe(false); + expect(result2).toBe(false); + }); + + it('should return false if the policy template has no deployment modes', () => { + const policyTemplate = { + name: 'template1', + title: 'Template 1', + description: '', + }; + + const result = isOnlyAgentlessPolicyTemplate(policyTemplate); + + expect(result).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/fleet/common/services/agentless_policy_helper.ts b/x-pack/plugins/fleet/common/services/agentless_policy_helper.ts index ede0dfa497187..7093875ae84f5 100644 --- a/x-pack/plugins/fleet/common/services/agentless_policy_helper.ts +++ b/x-pack/plugins/fleet/common/services/agentless_policy_helper.ts @@ -5,6 +5,47 @@ * 2.0. */ +import type { PackageInfo, RegistryPolicyTemplate } from '../types'; + +export const isAgentlessIntegration = ( + packageInfo: Pick<PackageInfo, 'policy_templates'> | undefined +) => { + if ( + packageInfo?.policy_templates && + packageInfo?.policy_templates.length > 0 && + !!packageInfo?.policy_templates.find( + (policyTemplate) => policyTemplate?.deployment_modes?.agentless.enabled === true + ) + ) { + return true; + } + return false; +}; + export const getAgentlessAgentPolicyNameFromPackagePolicyName = (packagePolicyName: string) => { return `Agentless policy for ${packagePolicyName}`; }; + +export const isOnlyAgentlessIntegration = ( + packageInfo: Pick<PackageInfo, 'policy_templates'> | undefined +) => { + if ( + packageInfo?.policy_templates && + packageInfo?.policy_templates.length > 0 && + packageInfo?.policy_templates.every((policyTemplate) => + isOnlyAgentlessPolicyTemplate(policyTemplate) + ) + ) { + return true; + } + return false; +}; + +export const isOnlyAgentlessPolicyTemplate = (policyTemplate: RegistryPolicyTemplate) => { + return Boolean( + policyTemplate.deployment_modes && + policyTemplate.deployment_modes.agentless.enabled === true && + (!policyTemplate.deployment_modes.default || + policyTemplate.deployment_modes.default.enabled === false) + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts index 95b4aa80a02bb..241dcfbb93f4e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts @@ -20,7 +20,10 @@ import { SetupTechnology } from '../../../../../types'; import { sendGetOneAgentPolicy, useStartServices } from '../../../../../hooks'; import { SelectedPolicyTab } from '../../components'; import { AGENTLESS_POLICY_ID } from '../../../../../../../../common/constants'; -import { getAgentlessAgentPolicyNameFromPackagePolicyName } from '../../../../../../../../common/services/agentless_policy_helper'; +import { + isAgentlessIntegration as isAgentlessIntegrationFn, + getAgentlessAgentPolicyNameFromPackagePolicyName, +} from '../../../../../../../../common/services/agentless_policy_helper'; export const useAgentless = () => { const config = useConfig(); @@ -45,14 +48,7 @@ export const useAgentless = () => { // When an integration has at least a policy template enabled for agentless const isAgentlessIntegration = (packageInfo: PackageInfo | undefined) => { - if ( - isAgentlessEnabled && - packageInfo?.policy_templates && - packageInfo?.policy_templates.length > 0 && - !!packageInfo?.policy_templates.find( - (policyTemplate) => policyTemplate?.deployment_modes?.agentless.enabled === true - ) - ) { + if (isAgentlessEnabled && isAgentlessIntegrationFn(packageInfo)) { return true; } return false; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx index dc055cec7fceb..1f2bdecf9e5ad 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx @@ -135,7 +135,6 @@ export function usePackagePolicySteps({ setNewAgentPolicy, updateAgentPolicies, setSelectedPolicyTab, - packageInfo, packagePolicy, isEditPage: true, agentPolicies, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx index c7b1f936e2424..2f506b30b2626 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx @@ -11,8 +11,10 @@ import { uniq } from 'lodash'; import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; import type { IntegrationPreferenceType } from '../../../components/integration_preference'; -import { useGetPackagesQuery, useGetCategoriesQuery } from '../../../../../hooks'; +import { useAgentless } from '../../../../../../fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology'; import { + useGetPackagesQuery, + useGetCategoriesQuery, useGetAppendCustomIntegrationsQuery, useGetReplacementCustomIntegrationsQuery, } from '../../../../../hooks'; @@ -28,6 +30,11 @@ import { isIntegrationPolicyTemplate, } from '../../../../../../../../common/services'; +import { + isOnlyAgentlessPolicyTemplate, + isOnlyAgentlessIntegration, +} from '../../../../../../../../common/services/agentless_policy_helper'; + import type { IntegrationCardItem } from '..'; import { ALL_CATEGORY } from '../category_facets'; @@ -103,6 +110,23 @@ const packageListToIntegrationsList = (packages: PackageList): PackageList => { }, []); }; +// Return filtered packages based on deployment mode, +// Currently filters out agentless only packages and policy templates if agentless is not available +const filterPackageListDeploymentModes = (packages: PackageList, isAgentlessEnabled: boolean) => { + return isAgentlessEnabled + ? packages + : packages + .filter((pkg) => { + return !isOnlyAgentlessIntegration(pkg); + }) + .map((pkg) => { + pkg.policy_templates = (pkg.policy_templates || []).filter((policyTemplate) => { + return !isOnlyAgentlessPolicyTemplate(policyTemplate); + }); + return pkg; + }); +}; + export type AvailablePackagesHookType = typeof useAvailablePackages; export const useAvailablePackages = ({ @@ -113,6 +137,7 @@ export const useAvailablePackages = ({ const [preference, setPreference] = useState<IntegrationPreferenceType>('recommended'); const { showIntegrationsSubcategories } = ExperimentalFeaturesService.get(); + const { isAgentlessEnabled } = useAgentless(); const { initialSelectedCategory, @@ -146,10 +171,13 @@ export const useAvailablePackages = ({ }); } - const eprIntegrationList = useMemo( - () => packageListToIntegrationsList(eprPackages?.items || []), - [eprPackages] - ); + const eprIntegrationList = useMemo(() => { + const filteredPackageList = + filterPackageListDeploymentModes(eprPackages?.items || [], isAgentlessEnabled) || []; + const integrations = packageListToIntegrationsList(filteredPackageList); + return integrations; + }, [eprPackages?.items, isAgentlessEnabled]); + const { data: replacementCustomIntegrations, isInitialLoading: isLoadingReplacmentCustomIntegrations, diff --git a/x-pack/plugins/fleet/public/hooks/use_config.ts b/x-pack/plugins/fleet/public/hooks/use_config.ts index db86ed66bba60..2df3ed5f38a54 100644 --- a/x-pack/plugins/fleet/public/hooks/use_config.ts +++ b/x-pack/plugins/fleet/public/hooks/use_config.ts @@ -9,12 +9,27 @@ import React, { useContext } from 'react'; import type { FleetConfigType } from '../plugin'; +import { useStartServices } from '.'; + export const ConfigContext = React.createContext<FleetConfigType | null>(null); -export function useConfig() { - const config = useContext(ConfigContext); - if (config === null) { - throw new Error('ConfigContext not initialized'); +export function useConfig(): FleetConfigType { + const { fleet } = useStartServices(); + const baseConfig = useContext(ConfigContext); + + // Downstream plugins may set `fleet` as part of the Kibana context + // which means that the Fleet config is exposed in that way + const pluginConfig = fleet?.config; + const config = baseConfig || pluginConfig || null; + + if (baseConfig === null && pluginConfig) { + // eslint-disable-next-line no-console + console.warn('Fleet ConfigContext not initialized, using from plugin context'); } + + if (!config) { + throw new Error('Fleet ConfigContext not initialized'); + } + return config; } diff --git a/x-pack/plugins/fleet/public/hooks/use_core.ts b/x-pack/plugins/fleet/public/hooks/use_core.ts index 0e65686ac38a7..314e7931eb363 100644 --- a/x-pack/plugins/fleet/public/hooks/use_core.ts +++ b/x-pack/plugins/fleet/public/hooks/use_core.ts @@ -7,10 +7,11 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { FleetStartServices } from '../plugin'; +import type { FleetStart, FleetStartServices } from '../plugin'; -export function useStartServices(): FleetStartServices { - const { services } = useKibana<FleetStartServices>(); +// Downstream plugins may set `fleet` as part of the Kibana context +export function useStartServices(): FleetStartServices & { fleet?: FleetStart } { + const { services } = useKibana<FleetStartServices & { fleet?: FleetStart }>(); if (services === null) { throw new Error('KibanaContextProvider not initialized'); } diff --git a/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts b/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts index e2490eecfd766..5af34f2b0bc04 100644 --- a/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts +++ b/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts @@ -9,6 +9,7 @@ import type { UIExtensionsStorage } from '../types'; import { createExtensionRegistrationCallback } from '../services/ui_extensions'; import type { MockedFleetStart } from './types'; +import { createConfigurationMock } from './plugin_configuration'; export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): MockedFleetStart => { return { @@ -41,6 +42,7 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo writeIntegrationPolicies: true, }, }, + config: createConfigurationMock(), hooks: { epm: { getBulkAssets: jest.fn() } }, }; }; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index ce922f838ae4e..ced047f7cc0c4 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -102,6 +102,7 @@ export interface FleetSetup {} export interface FleetStart { /** Authorization for the current user */ authz: FleetAuthz; + config: FleetConfigType; registerExtension: UIExtensionRegistrationCallback; isInitialized: () => Promise<true>; hooks: { @@ -356,7 +357,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep // capabilities.fleetv2 returns fleet privileges and capabilities.fleet returns integrations privileges return { authz, - + config: this.config, isInitialized: once(async () => { const permissionsResponse = await getPermissions(); diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index 31e4b9d6704c7..2bdd118e7fb40 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -45,6 +45,7 @@ import { PackageSavedObjectConflictError, FleetTooManyRequestsError, AgentlessPolicyExistsRequestError, + PackageInvalidDeploymentMode, PackagePolicyContentPackageError, } from '.'; @@ -61,6 +62,9 @@ interface IngestErrorHandlerParams { // this type is based on BadRequest values observed while debugging https://github.com/elastic/kibana/issues/75862 const getHTTPResponseCode = (error: FleetError): number => { // Bad Request + if (error instanceof PackageInvalidDeploymentMode) { + return 400; + } if (error instanceof PackageFailedVerificationError) { return 400; } diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index de528f082c096..abc36f7df9692 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -29,6 +29,7 @@ export class RegistryResponseError extends RegistryError { // Package errors +export class PackageInvalidDeploymentMode extends FleetError {} export class PackageOutdatedError extends FleetError {} export class PackageFailedVerificationError extends FleetError { constructor(pkgName: string, pkgVersion: string) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts index a0bd8c8d77fe6..709e0d84d70fc 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -17,6 +17,7 @@ import { licenseService } from '../../license'; import { auditLoggingService } from '../../audit_logging'; import { appContextService } from '../../app_context'; import { ConcurrentInstallOperationError, FleetError, PackageNotFoundError } from '../../../errors'; +import { isAgentlessEnabled, isOnlyAgentlessIntegration } from '../../utils/agentless'; import * as Registry from '../registry'; import { dataStreamService } from '../../data_streams'; @@ -102,6 +103,13 @@ jest.mock('../archive', () => { }); jest.mock('../../audit_logging'); +jest.mock('../../utils/agentless', () => { + return { + isAgentlessEnabled: jest.fn(), + isOnlyAgentlessIntegration: jest.fn(), + }; +}); + const mockGetBundledPackageByPkgKey = jest.mocked(getBundledPackageByPkgKey); const mockedAuditLoggingService = jest.mocked(auditLoggingService); @@ -357,13 +365,72 @@ describe('install', () => { expect(response.status).toEqual('already_installed'); }); - // failing + describe('agentless', () => { + beforeEach(() => { + jest.mocked(appContextService.getConfig).mockClear(); + jest.spyOn(licenseService, 'hasAtLeast').mockClear(); + jest.mocked(isAgentlessEnabled).mockClear(); + jest.mocked(isOnlyAgentlessIntegration).mockClear(); + }); + + it('should not allow to install agentless only integration if agentless is not enabled', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + jest.mocked(isAgentlessEnabled).mockReturnValueOnce(false); + jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(true); + + const response = await installPackage({ + spaceId: DEFAULT_SPACE_ID, + installSource: 'registry', + pkgkey: 'test_package', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + expect(response.error).toBeDefined(); + expect(response.error!.message).toEqual( + 'test_package contains agentless policy templates, agentless is not available on this deployment' + ); + }); + + it('should allow to install agentless only integration if agentless is not enabled but using force flag', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + jest.mocked(isAgentlessEnabled).mockReturnValueOnce(false); + jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(true); + + const response = await installPackage({ + spaceId: DEFAULT_SPACE_ID, + installSource: 'registry', + pkgkey: 'test_package', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + force: true, + }); + expect(response.error).toBeUndefined(); + }); + + it('should allow to install agentless only integration if agentless is enabled', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + jest.mocked(isAgentlessEnabled).mockReturnValueOnce(true); + jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(true); + + const response = await installPackage({ + spaceId: DEFAULT_SPACE_ID, + installSource: 'registry', + pkgkey: 'test_package', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + expect(response.error).toBeUndefined(); + }); + }); + it('should allow to install fleet_server if internal.fleetServerStandalone is configured', async () => { jest.mocked(appContextService.getConfig).mockReturnValueOnce({ internal: { fleetServerStandalone: true, }, } as any); + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValueOnce(true); + jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(false); const response = await installPackage({ spaceId: DEFAULT_SPACE_ID, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 65f1a75f76f84..1ea6f29cad839 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -60,6 +60,7 @@ import { FleetUnauthorizedError, PackageNotFoundError, FleetTooManyRequestsError, + PackageInvalidDeploymentMode, } from '../../../errors'; import { PACKAGES_SAVED_OBJECT_TYPE, @@ -82,6 +83,8 @@ import { sendTelemetryEvents, UpdateEventType } from '../../upgrade_sender'; import { auditLoggingService } from '../../audit_logging'; import { getFilteredInstallPackages } from '../filtered_packages'; +import { isAgentlessEnabled, isOnlyAgentlessIntegration } from '../../utils/agentless'; + import { _stateMachineInstallPackage } from './install_state_machine/_state_machine_package_install'; import { formatVerificationResultForSO } from './package_verification'; @@ -507,6 +510,21 @@ async function installPackageFromRegistry({ }` ); } + + // only allow install of agentless packages if agentless is enabled, or if using force flag + const agentlessEnabled = isAgentlessEnabled(); + const agentlessOnlyIntegration = isOnlyAgentlessIntegration(packageInfo); + if (!agentlessEnabled && agentlessOnlyIntegration) { + if (!force) { + throw new PackageInvalidDeploymentMode( + `${pkgkey} contains agentless policy templates, agentless is not available on this deployment` + ); + } + logger.debug( + `${pkgkey} contains agentless policy templates, agentless is not available on this deployment but installing anyway due to force flag` + ); + } + return await installPackageWithStateMachine({ pkgName, pkgVersion, diff --git a/x-pack/plugins/fleet/server/services/utils/agentless.ts b/x-pack/plugins/fleet/server/services/utils/agentless.ts index 4c27d583d9a79..c43f10db16b46 100644 --- a/x-pack/plugins/fleet/server/services/utils/agentless.ts +++ b/x-pack/plugins/fleet/server/services/utils/agentless.ts @@ -7,14 +7,15 @@ import { appContextService } from '..'; import type { FleetConfigType } from '../../config'; +export { isOnlyAgentlessIntegration } from '../../../common/services/agentless_policy_helper'; export const isAgentlessApiEnabled = () => { - const cloudSetup = appContextService.getCloud(); + const cloudSetup = appContextService.getCloud && appContextService.getCloud(); const isHosted = cloudSetup?.isCloudEnabled || cloudSetup?.isServerlessEnabled; return Boolean(isHosted && appContextService.getConfig()?.agentless?.enabled); }; export const isDefaultAgentlessPolicyEnabled = () => { - const cloudSetup = appContextService.getCloud(); + const cloudSetup = appContextService.getCloud && appContextService.getCloud(); return Boolean( cloudSetup?.isServerlessEnabled && appContextService.getExperimentalFeatures().agentless ); @@ -44,7 +45,7 @@ export const prependAgentlessApiBasePathToEndpoint = ( agentlessConfig: FleetConfigType['agentless'], endpoint: AgentlessApiEndpoints ) => { - const cloudSetup = appContextService.getCloud(); + const cloudSetup = appContextService.getCloud && appContextService.getCloud(); const endpointPrefix = cloudSetup?.isServerlessEnabled ? AGENTLESS_SERVERLESS_API_BASE_PATH : AGENTLESS_ESS_API_BASE_PATH; From 577599cf9f43c900062830fdb03787f7b419d7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Wed, 16 Oct 2024 02:21:44 +0200 Subject: [PATCH 76/84] chore(): do not cancel ld-code-references jobs (#196388) --- .github/workflows/launchdarkly-code-references.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/launchdarkly-code-references.yml b/.github/workflows/launchdarkly-code-references.yml index 1034d25b29e85..23b877ce40d06 100644 --- a/.github/workflows/launchdarkly-code-references.yml +++ b/.github/workflows/launchdarkly-code-references.yml @@ -5,11 +5,6 @@ on: branches: - 'main' -# cancel in-flight workflow run if another push was triggered -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: launchDarklyCodeReferences: name: LaunchDarkly Code References From 267efdf31fe9ae314b0bed99bc23db5452a2aaa3 Mon Sep 17 00:00:00 2001 From: Ying Mao <ying.mao@elastic.co> Date: Tue, 15 Oct 2024 20:24:52 -0400 Subject: [PATCH 77/84] [Response Ops][Task Manager] Onboard 12.5% of ECH clusters to use `mget` task claiming (#196317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves https://github.com/elastic/response-ops-team/issues/239 ## Summary Deployed to cloud: deployment ID was `ab4e88d139f93d43024837d96144e7d4`. Since the deployment ID starts with an `a`, this should start with `mget` and I can see in the logs with the latest push that this is true <img width="2190" alt="Screenshot 2024-10-15 at 2 59 20 PM" src="https://github.com/user-attachments/assets/079bc4d8-365e-4ba6-b7a9-59fe506283d9"> Deployed to serverless: project ID was `d33d22a94ce246d091220eace2c4e4bb`. See in the logs: `Using claim strategy mget as configured for deployment d33d22a94ce246d091220eace2c4e4bb` Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../task_manager/server/config.test.ts | 3 - x-pack/plugins/task_manager/server/config.ts | 2 +- .../server/lib/set_claim_strategy.test.ts | 197 ++++++++++++++++++ .../server/lib/set_claim_strategy.ts | 76 +++++++ x-pack/plugins/task_manager/server/plugin.ts | 21 +- .../task_manager/server/polling_lifecycle.ts | 8 +- .../server/saved_objects/index.ts | 6 +- 7 files changed, 294 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugins/task_manager/server/lib/set_claim_strategy.test.ts create mode 100644 x-pack/plugins/task_manager/server/lib/set_claim_strategy.ts diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index 34dd5f1c6fbff..aefbdaa9c8c56 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -14,7 +14,6 @@ describe('config validation', () => { Object { "allow_reading_invalid_state": true, "auto_calculate_default_ech_capacity": false, - "claim_strategy": "update_by_query", "discovery": Object { "active_nodes_lookback": "30s", "interval": 10000, @@ -77,7 +76,6 @@ describe('config validation', () => { Object { "allow_reading_invalid_state": true, "auto_calculate_default_ech_capacity": false, - "claim_strategy": "update_by_query", "discovery": Object { "active_nodes_lookback": "30s", "interval": 10000, @@ -138,7 +136,6 @@ describe('config validation', () => { Object { "allow_reading_invalid_state": true, "auto_calculate_default_ech_capacity": false, - "claim_strategy": "update_by_query", "discovery": Object { "active_nodes_lookback": "30s", "interval": 10000, diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index f640ed2165f22..3eff1b507107c 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -202,7 +202,7 @@ export const configSchema = schema.object( max: 100, min: 1, }), - claim_strategy: schema.string({ defaultValue: CLAIM_STRATEGY_UPDATE_BY_QUERY }), + claim_strategy: schema.maybe(schema.string()), request_timeouts: requestTimeoutsConfig, auto_calculate_default_ech_capacity: schema.boolean({ defaultValue: false }), }, diff --git a/x-pack/plugins/task_manager/server/lib/set_claim_strategy.test.ts b/x-pack/plugins/task_manager/server/lib/set_claim_strategy.test.ts new file mode 100644 index 0000000000000..bb3d679299d33 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/set_claim_strategy.test.ts @@ -0,0 +1,197 @@ +/* + * 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 { + CLAIM_STRATEGY_MGET, + CLAIM_STRATEGY_UPDATE_BY_QUERY, + DEFAULT_POLL_INTERVAL, + MGET_DEFAULT_POLL_INTERVAL, +} from '../config'; +import { mockLogger } from '../test_utils'; +import { setClaimStrategy } from './set_claim_strategy'; + +const getConfigWithoutClaimStrategy = () => ({ + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, + capacity: 10, + max_attempts: 9, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + request_timeouts: { + update_by_query: 1000, + }, + poll_interval: DEFAULT_POLL_INTERVAL, + auto_calculate_default_ech_capacity: false, +}); + +const logger = mockLogger(); + +const deploymentIdUpdateByQuery = 'd2f0e7c6bc464a9b8b16e5730b9c40f9'; +const deploymentIdMget = 'ab4e88d139f93d43024837d96144e7d4'; +describe('setClaimStrategy', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + for (const isServerless of [true, false]) { + for (const isCloud of [true, false]) { + for (const deploymentId of [undefined, deploymentIdMget, deploymentIdUpdateByQuery]) { + for (const configuredStrategy of [CLAIM_STRATEGY_MGET, CLAIM_STRATEGY_UPDATE_BY_QUERY]) { + test(`should return config as is when claim strategy is already defined: isServerless=${isServerless}, isCloud=${isCloud}, deploymentId=${deploymentId}`, () => { + const config = { + ...getConfigWithoutClaimStrategy(), + claim_strategy: configuredStrategy, + }; + + const returnedConfig = setClaimStrategy({ + config, + logger, + isCloud, + isServerless, + deploymentId, + }); + + expect(returnedConfig).toStrictEqual(config); + if (deploymentId) { + expect(logger.info).toHaveBeenCalledWith( + `Using claim strategy ${configuredStrategy} as configured for deployment ${deploymentId}` + ); + } else { + expect(logger.info).toHaveBeenCalledWith( + `Using claim strategy ${configuredStrategy} as configured` + ); + } + }); + } + } + } + } + + for (const isCloud of [true, false]) { + for (const deploymentId of [undefined, deploymentIdMget, deploymentIdUpdateByQuery]) { + test(`should set claim strategy to mget if in serverless: isCloud=${isCloud}, deploymentId=${deploymentId}`, () => { + const config = getConfigWithoutClaimStrategy(); + const returnedConfig = setClaimStrategy({ + config, + logger, + isCloud, + isServerless: true, + deploymentId, + }); + + expect(returnedConfig.claim_strategy).toBe(CLAIM_STRATEGY_MGET); + expect(returnedConfig.poll_interval).toBe(MGET_DEFAULT_POLL_INTERVAL); + + if (deploymentId) { + expect(logger.info).toHaveBeenCalledWith( + `Setting claim strategy to mget for serverless deployment ${deploymentId}` + ); + } else { + expect(logger.info).toHaveBeenCalledWith(`Setting claim strategy to mget`); + } + }); + } + } + + test(`should set claim strategy to update_by_query if not cloud and not serverless`, () => { + const config = getConfigWithoutClaimStrategy(); + const returnedConfig = setClaimStrategy({ + config, + logger, + isCloud: false, + isServerless: false, + }); + + expect(returnedConfig.claim_strategy).toBe(CLAIM_STRATEGY_UPDATE_BY_QUERY); + expect(returnedConfig.poll_interval).toBe(DEFAULT_POLL_INTERVAL); + + expect(logger.info).not.toHaveBeenCalled(); + }); + + test(`should set claim strategy to update_by_query if cloud and not serverless with undefined deploymentId`, () => { + const config = getConfigWithoutClaimStrategy(); + const returnedConfig = setClaimStrategy({ + config, + logger, + isCloud: true, + isServerless: false, + }); + + expect(returnedConfig.claim_strategy).toBe(CLAIM_STRATEGY_UPDATE_BY_QUERY); + expect(returnedConfig.poll_interval).toBe(DEFAULT_POLL_INTERVAL); + + expect(logger.info).not.toHaveBeenCalled(); + }); + + test(`should set claim strategy to update_by_query if cloud and not serverless and deploymentId does not start with a or b`, () => { + const config = getConfigWithoutClaimStrategy(); + const returnedConfig = setClaimStrategy({ + config, + logger, + isCloud: true, + isServerless: false, + deploymentId: deploymentIdUpdateByQuery, + }); + + expect(returnedConfig.claim_strategy).toBe(CLAIM_STRATEGY_UPDATE_BY_QUERY); + expect(returnedConfig.poll_interval).toBe(DEFAULT_POLL_INTERVAL); + + expect(logger.info).toHaveBeenCalledWith( + `Setting claim strategy to update_by_query for deployment ${deploymentIdUpdateByQuery}` + ); + }); + + test(`should set claim strategy to mget if cloud and not serverless and deploymentId starts with a or b`, () => { + const config = getConfigWithoutClaimStrategy(); + const returnedConfig = setClaimStrategy({ + config, + logger, + isCloud: true, + isServerless: false, + deploymentId: deploymentIdMget, + }); + + expect(returnedConfig.claim_strategy).toBe(CLAIM_STRATEGY_MGET); + expect(returnedConfig.poll_interval).toBe(MGET_DEFAULT_POLL_INTERVAL); + + expect(logger.info).toHaveBeenCalledWith( + `Setting claim strategy to mget for deployment ${deploymentIdMget}` + ); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/set_claim_strategy.ts b/x-pack/plugins/task_manager/server/lib/set_claim_strategy.ts new file mode 100644 index 0000000000000..52d71d25c7387 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/set_claim_strategy.ts @@ -0,0 +1,76 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { + CLAIM_STRATEGY_MGET, + CLAIM_STRATEGY_UPDATE_BY_QUERY, + DEFAULT_POLL_INTERVAL, + MGET_DEFAULT_POLL_INTERVAL, + TaskManagerConfig, +} from '../config'; + +interface SetClaimStrategyOpts { + config: TaskManagerConfig; + deploymentId?: string; + isServerless: boolean; + isCloud: boolean; + logger: Logger; +} + +export function setClaimStrategy(opts: SetClaimStrategyOpts): TaskManagerConfig { + // if the claim strategy is already defined, return immediately + if (opts.config.claim_strategy) { + opts.logger.info( + `Using claim strategy ${opts.config.claim_strategy} as configured${ + opts.deploymentId ? ` for deployment ${opts.deploymentId}` : '' + }` + ); + return opts.config; + } + + if (opts.isServerless) { + // use mget for serverless + opts.logger.info( + `Setting claim strategy to mget${ + opts.deploymentId ? ` for serverless deployment ${opts.deploymentId}` : '' + }` + ); + return { + ...opts.config, + claim_strategy: CLAIM_STRATEGY_MGET, + poll_interval: MGET_DEFAULT_POLL_INTERVAL, + }; + } + + let defaultToMget = false; + + if (opts.isCloud && !opts.isServerless && opts.deploymentId) { + defaultToMget = opts.deploymentId.startsWith('a') || opts.deploymentId.startsWith('b'); + if (defaultToMget) { + opts.logger.info(`Setting claim strategy to mget for deployment ${opts.deploymentId}`); + } else { + opts.logger.info( + `Setting claim strategy to update_by_query for deployment ${opts.deploymentId}` + ); + } + } + + if (defaultToMget) { + return { + ...opts.config, + claim_strategy: CLAIM_STRATEGY_MGET, + poll_interval: MGET_DEFAULT_POLL_INTERVAL, + }; + } + + return { + ...opts.config, + claim_strategy: CLAIM_STRATEGY_UPDATE_BY_QUERY, + poll_interval: DEFAULT_POLL_INTERVAL, + }; +} diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 45960195be216..61731c4ae82f3 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,7 +18,7 @@ import { ServiceStatusLevels, CoreStatus, } from '@kbn/core/server'; -import type { CloudStart } from '@kbn/cloud-plugin/server'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server'; import { registerDeleteInactiveNodesTaskDefinition, scheduleDeleteInactiveNodesTaskDefinition, @@ -45,6 +45,7 @@ import { metricsStream, Metrics } from './metrics'; import { TaskManagerMetricsCollector } from './metrics/task_metrics_collector'; import { TaskPartitioner } from './lib/task_partitioner'; import { getDefaultCapacity } from './lib/get_default_capacity'; +import { setClaimStrategy } from './lib/set_claim_strategy'; export interface TaskManagerSetupContract { /** @@ -126,10 +127,18 @@ export class TaskManagerPlugin public setup( core: CoreSetup<TaskManagerStartContract, unknown>, - plugins: { usageCollection?: UsageCollectionSetup } + plugins: { cloud?: CloudSetup; usageCollection?: UsageCollectionSetup } ): TaskManagerSetupContract { this.elasticsearchAndSOAvailability$ = getElasticsearchAndSOAvailability(core.status.core$); + this.config = setClaimStrategy({ + config: this.config, + deploymentId: plugins.cloud?.deploymentId, + isServerless: this.initContext.env.packageInfo.buildFlavor === 'serverless', + isCloud: plugins.cloud?.isCloudEnabled ?? false, + logger: this.logger, + }); + core.metrics .getOpsMetrics$() .pipe(distinctUntilChanged()) @@ -137,7 +146,7 @@ export class TaskManagerPlugin this.heapSizeLimit = metrics.process.memory.heap.size_limit; }); - setupSavedObjects(core.savedObjects, this.config); + setupSavedObjects(core.savedObjects); this.taskManagerId = this.initContext.env.instanceUuid; if (!this.taskManagerId) { @@ -301,9 +310,9 @@ export class TaskManagerPlugin this.config!.claim_strategy } isBackgroundTaskNodeOnly=${this.isNodeBackgroundTasksOnly()} heapSizeLimit=${ this.heapSizeLimit - } defaultCapacity=${defaultCapacity} autoCalculateDefaultEchCapacity=${ - this.config.auto_calculate_default_ech_capacity - }` + } defaultCapacity=${defaultCapacity} pollingInterval=${ + this.config!.poll_interval + } autoCalculateDefaultEchCapacity=${this.config.auto_calculate_default_ech_capacity}` ); const managedConfiguration = createManagedConfiguration({ diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 7d8be75c2330c..3cb6802f43eb1 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -14,7 +14,7 @@ import type { Logger, ExecutionContextStart } from '@kbn/core/server'; import { Result, asErr, mapErr, asOk, map, mapOk } from './lib/result_type'; import { ManagedConfiguration } from './lib/create_managed_configuration'; -import { TaskManagerConfig, CLAIM_STRATEGY_UPDATE_BY_QUERY } from './config'; +import { CLAIM_STRATEGY_UPDATE_BY_QUERY, TaskManagerConfig } from './config'; import { TaskMarkRunning, @@ -141,7 +141,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven this.pool = new TaskPool({ logger, - strategy: config.claim_strategy, + strategy: config.claim_strategy!, capacity$: capacityConfiguration$, definitions: this.definitions, }); @@ -149,7 +149,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven this.taskClaiming = new TaskClaiming({ taskStore, - strategy: config.claim_strategy, + strategy: config.claim_strategy!, maxAttempts: config.max_attempts, excludedTaskTypes: config.unsafe.exclude_task_types, definitions, @@ -238,7 +238,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven usageCounter: this.usageCounter, config: this.config, allowReadingInvalidState: this.config.allow_reading_invalid_state, - strategy: this.config.claim_strategy, + strategy: this.config.claim_strategy!, getPollInterval: () => this.currentPollInterval, }); }; diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts index dc1cd97677767..5c0f8b9a0776d 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts @@ -9,7 +9,6 @@ import type { SavedObjectsServiceSetup } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { backgroundTaskNodeMapping, taskMappings } from './mappings'; import { getMigrations } from './migrations'; -import { TaskManagerConfig } from '../config'; import { getOldestIdleActionTask } from '../queries/oldest_idle_action_task'; import { TASK_MANAGER_INDEX } from '../constants'; import { backgroundTaskNodeModelVersions, taskModelVersions } from './model_versions'; @@ -17,10 +16,7 @@ import { backgroundTaskNodeModelVersions, taskModelVersions } from './model_vers export const TASK_SO_NAME = 'task'; export const BACKGROUND_TASK_NODE_SO_NAME = 'background-task-node'; -export function setupSavedObjects( - savedObjects: SavedObjectsServiceSetup, - config: TaskManagerConfig -) { +export function setupSavedObjects(savedObjects: SavedObjectsServiceSetup) { savedObjects.registerType({ name: TASK_SO_NAME, namespaceType: 'agnostic', From 5ae7a61d935e3c1778ee830a5c1ee5055abf44a0 Mon Sep 17 00:00:00 2001 From: Charlotte Alexandra Wilson <CAWilson94@users.noreply.github.com> Date: Wed, 16 Oct 2024 01:29:35 +0100 Subject: [PATCH 78/84] Feature/remove asset criticality flag (#196270) ## Summary It removes the asset criticality advanced setting, which enables the feature by default for all users. Deleted settings: ![Screenshot 2024-10-15 at 14 54 48](https://github.com/user-attachments/assets/103c3f04-fd7e-45cf-ac74-93e1eef341fa) ### How to test it? * Start Kibana with security data * Inside security solution / manage, you should be able to find the Asset Criticality page ![Screenshot 2024-10-15 at 14 57 14](https://github.com/user-attachments/assets/7ddcee91-ad76-4d8f-b14a-bacc4ba31172) * You should see the asset critically section when opening an entity flyout (explore or host page) <img width="400" src="https://github.com/user-attachments/assets/3a9ee545-566c-4687-af16-f31bd93bdc20" /> * The risk score should be updated if you update an entity's asset criticality. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: machadoum <pablo.nevesmachado@elastic.co> Co-authored-by: jaredburgettelastic <jared.burgett@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../settings/security_project/index.ts | 1 - .../security_solution/common/constants.ts | 3 - .../use_asset_criticality.test.ts | 9 --- .../use_asset_criticality.ts | 10 ++-- .../tabs/risk_inputs/risk_inputs_tab.tsx | 8 +-- .../components/risk_summary_flyout/common.tsx | 60 ++++++++----------- .../risk_summary_flyout/risk_summary.test.tsx | 41 ------------- .../risk_summary_flyout/risk_summary.tsx | 15 +---- .../pages/entity_store_management_page.tsx | 10 +--- .../hosts/components/hosts_table/columns.tsx | 37 ++++++------ .../components/hosts_table/index.test.tsx | 25 -------- .../hosts/components/hosts_table/index.tsx | 18 +----- .../users/components/all_users/index.test.tsx | 6 +- .../users/components/all_users/index.tsx | 46 +++++++------- .../utils/enrichments/index.test.ts | 6 -- .../rule_types/utils/enrichments/index.ts | 54 +++++++---------- .../asset_criticality_service.mock.ts | 1 - .../asset_criticality_service.ts | 4 -- .../asset_criticality/routes/bulk_upload.ts | 3 - .../asset_criticality/routes/delete.ts | 3 - .../asset_criticality/routes/get.ts | 3 - .../asset_criticality/routes/list.ts | 3 - .../asset_criticality/routes/privileges.ts | 4 -- .../asset_criticality/routes/status.ts | 3 - .../asset_criticality/routes/upload_csv.ts | 3 - .../asset_criticality/routes/upsert.ts | 3 - .../risk_score/calculate_risk_scores.ts | 7 --- .../security_solution/server/ui_settings.ts | 19 ------ .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../execution_logic/custom_query.ts | 5 -- .../execution_logic/eql.ts | 9 +-- .../execution_logic/eql_alert_suppression.ts | 10 +--- .../execution_logic/esql.ts | 5 -- .../execution_logic/esql_suppression.ts | 5 -- .../execution_logic/indicator_match.ts | 5 -- .../indicator_match_alert_suppression.ts | 5 -- .../execution_logic/machine_learning.ts | 9 +-- .../machine_learning_alert_suppression.ts | 10 +--- .../execution_logic/new_terms.ts | 6 +- .../new_terms_alert_suppression.ts | 5 -- .../execution_logic/threshold.ts | 5 -- .../threshold_alert_suppression.ts | 5 -- .../asset_criticality.ts | 55 ----------------- .../asset_criticality_csv_upload.ts | 15 ----- .../asset_criticality_privileges.ts | 9 +-- .../risk_score_entity_calculation.ts | 5 -- .../risk_score_preview.ts | 6 -- .../utils/asset_criticality.ts | 40 ------------- .../asset_criticality_upload_page.cy.ts | 2 - .../e2e/entity_analytics/entity_flyout.cy.ts | 2 - .../api_calls/kibana_advanced_settings.ts | 5 -- 53 files changed, 106 insertions(+), 528 deletions(-) diff --git a/packages/serverless/settings/security_project/index.ts b/packages/serverless/settings/security_project/index.ts index dbbf6e506eda8..0fd820640bb98 100644 --- a/packages/serverless/settings/security_project/index.ts +++ b/packages/serverless/settings/security_project/index.ts @@ -23,5 +23,4 @@ export const SECURITY_PROJECT_SETTINGS = [ settings.SECURITY_SOLUTION_NEWS_FEED_URL_ID, settings.SECURITY_SOLUTION_ENABLE_NEWS_FEED_ID, settings.SECURITY_SOLUTION_DEFAULT_ALERT_TAGS_KEY, - settings.SECURITY_SOLUTION_ENABLE_ASSET_CRITICALITY_SETTING, ]; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index d4cb8f088df88..877214641dc1e 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -196,9 +196,6 @@ export const EXTENDED_RULE_EXECUTION_LOGGING_ENABLED_SETTING = export const EXTENDED_RULE_EXECUTION_LOGGING_MIN_LEVEL_SETTING = 'securitySolution:extendedRuleExecutionLoggingMinLevel' as const; -/** This Kibana Advanced Setting allows users to enable/disable the Asset Criticality feature */ -export const ENABLE_ASSET_CRITICALITY_SETTING = 'securitySolution:enableAssetCriticality' as const; - /** This Kibana Advanced Setting allows users to exclude selected data tiers from search during rule execution */ export const EXCLUDED_DATA_TIERS_FOR_RULE_EXECUTION = 'securitySolution:excludedDataTiersForRuleExecution' as const; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts index bd6a6aae0604b..d4671a5bc628a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts @@ -60,15 +60,6 @@ describe('useAssetCriticality', () => { expect(mockFetchAssetCriticalityPrivileges).toHaveBeenCalled(); }); - - it('does not call privileges API when UI Settings is disabled', async () => { - mockUseHasSecurityCapability.mockReturnValue(true); - mockUseUiSettings.mockReturnValue([false]); - - await renderQuery(() => useAssetCriticalityPrivileges('test_entity_name'), 'isSuccess'); - - expect(mockFetchAssetCriticalityPrivileges).not.toHaveBeenCalled(); - }); }); describe('useAssetCriticalityData', () => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts index 9bd67dfed731e..d5ecde239f35a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts @@ -7,11 +7,9 @@ import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import type { SecurityAppError } from '@kbn/securitysolution-t-grid'; import type { EntityAnalyticsPrivileges } from '../../../../common/api/entity_analytics'; import type { CriticalityLevelWithUnassigned } from '../../../../common/entity_analytics/asset_criticality/types'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../common/constants'; import { useHasSecurityCapability } from '../../../helper_hooks'; import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics/asset_criticality'; import type { AssetCriticality, DeleteAssetCriticalityResponse } from '../../api/api'; @@ -34,12 +32,12 @@ export const useAssetCriticalityPrivileges = ( ): UseQueryResult<EntityAnalyticsPrivileges, SecurityAppError> => { const { fetchAssetCriticalityPrivileges } = useEntityAnalyticsRoutes(); const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); - const isEnabled = isAssetCriticalityEnabled && hasEntityAnalyticsCapability; return useQuery({ - queryKey: [ASSET_CRITICALITY_KEY, PRIVILEGES_KEY, queryKey, isEnabled], - queryFn: isEnabled ? fetchAssetCriticalityPrivileges : () => nonAuthorizedResponse, + queryKey: [ASSET_CRITICALITY_KEY, PRIVILEGES_KEY, queryKey, hasEntityAnalyticsCapability], + queryFn: hasEntityAnalyticsCapability + ? fetchAssetCriticalityPrivileges + : () => nonAuthorizedResponse, }); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab.tsx index 7f59ac7efbf42..78010434ee593 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab.tsx @@ -10,7 +10,6 @@ import { EuiSpacer, EuiInMemoryTable, EuiTitle, EuiCallOut } from '@elastic/eui' import type { ReactNode } from 'react'; import React, { useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; import { get } from 'lodash/fp'; @@ -24,7 +23,6 @@ import type { UseRiskContributingAlertsResult, } from '../../../../hooks/use_risk_contributing_alerts'; import { useRiskContributingAlerts } from '../../../../hooks/use_risk_contributing_alerts'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../../../common/constants'; import { PreferenceFormattedDate } from '../../../../../common/components/formatted_date'; import { useRiskScore } from '../../../../api/hooks/use_risk_score'; @@ -177,8 +175,6 @@ export const RiskInputsTab = ({ entityType, entityName, scopeId }: RiskInputsTab [isPreviewEnabled, scopeId] ); - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); - if (riskScoreError) { return ( <EuiCallOut @@ -229,9 +225,7 @@ export const RiskInputsTab = ({ entityType, entityName, scopeId }: RiskInputsTab return ( <> - {isAssetCriticalityEnabled && ( - <ContextsSection loading={loadingRiskScore} riskScore={riskScore} /> - )} + <ContextsSection loading={loadingRiskScore} riskScore={riskScore} /> <EuiSpacer size="m" /> {riskInputsAlertSection} </> diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx index ccf98e10a76af..1f9832b79654c 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx @@ -24,9 +24,7 @@ interface EntityData { risk: RiskStats; } -export const buildColumns: (showFooter: boolean) => Array<EuiBasicTableColumn<TableItem>> = ( - showFooter -) => [ +export const buildColumns: () => Array<EuiBasicTableColumn<TableItem>> = () => [ { field: 'category', name: ( @@ -38,12 +36,12 @@ export const buildColumns: (showFooter: boolean) => Array<EuiBasicTableColumn<Ta truncateText: false, mobileOptions: { show: true }, sortable: true, - footer: showFooter ? ( + footer: ( <FormattedMessage id="xpack.securitySolution.flyout.entityDetails.categoryColumnFooterLabel" defaultMessage="Result" /> - ) : undefined, + ), }, { field: 'score', @@ -59,12 +57,11 @@ export const buildColumns: (showFooter: boolean) => Array<EuiBasicTableColumn<Ta dataType: 'number', align: 'right', render: formatRiskScore, - footer: (props) => - showFooter ? ( - <span data-test-subj="risk-summary-result-score"> - {formatRiskScore(sumBy((i) => i.score, props.items))} - </span> - ) : undefined, + footer: (props) => ( + <span data-test-subj="risk-summary-result-score"> + {formatRiskScore(sumBy((i) => i.score, props.items))} + </span> + ), }, { field: 'count', @@ -79,19 +76,15 @@ export const buildColumns: (showFooter: boolean) => Array<EuiBasicTableColumn<Ta sortable: true, dataType: 'number', align: 'right', - footer: (props) => - showFooter ? ( - <span data-test-subj="risk-summary-result-count"> - {sumBy((i) => i.count ?? 0, props.items)} - </span> - ) : undefined, + footer: (props) => ( + <span data-test-subj="risk-summary-result-count"> + {sumBy((i) => i.count ?? 0, props.items)} + </span> + ), }, ]; -export const getItems: ( - entityData: EntityData | undefined, - isAssetCriticalityEnabled: boolean -) => TableItem[] = (entityData, isAssetCriticalityEnabled) => { +export const getItems: (entityData: EntityData | undefined) => TableItem[] = (entityData) => { return [ { category: i18n.translate('xpack.securitySolution.flyout.entityDetails.alertsGroupLabel', { @@ -100,20 +93,17 @@ export const getItems: ( score: entityData?.risk.category_1_score ?? 0, count: entityData?.risk.category_1_count ?? 0, }, - ...(isAssetCriticalityEnabled - ? [ - { - category: i18n.translate( - 'xpack.securitySolution.flyout.entityDetails.assetCriticalityGroupLabel', - { - defaultMessage: 'Asset Criticality', - } - ), - score: entityData?.risk.category_2_score ?? 0, - count: undefined, - }, - ] - : []), + + { + category: i18n.translate( + 'xpack.securitySolution.flyout.entityDetails.assetCriticalityGroupLabel', + { + defaultMessage: 'Asset Criticality', + } + ), + score: entityData?.risk.category_2_score ?? 0, + count: undefined, + }, ]; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx index aa0eab9902520..494cfd5c16b2a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx @@ -27,53 +27,12 @@ jest.mock('../../../common/components/visualization_actions/visualization_embedd mockVisualizationEmbeddable(props), })); -const mockUseUiSetting = jest.fn().mockReturnValue([false]); - -jest.mock('@kbn/kibana-react-plugin/public', () => { - const original = jest.requireActual('@kbn/kibana-react-plugin/public'); - return { - ...original, - useUiSetting$: () => mockUseUiSetting(), - }; -}); - describe('FlyoutRiskSummary', () => { beforeEach(() => { mockVisualizationEmbeddable.mockClear(); }); - it('renders risk summary table with alerts only', () => { - const { getByTestId, queryByTestId } = render( - <TestProviders> - <FlyoutRiskSummary - riskScoreData={mockHostRiskScoreState} - queryId={'testQuery'} - openDetailsPanel={() => {}} - recalculatingScore={false} - /> - </TestProviders> - ); - - expect(getByTestId('risk-summary-table')).toBeInTheDocument(); - - // Alerts - expect(getByTestId('risk-summary-table')).toHaveTextContent( - `${mockHostRiskScoreState.data?.[0].host.risk.category_1_count}` - ); - - // Context - expect(getByTestId('risk-summary-table')).not.toHaveTextContent( - `${mockHostRiskScoreState.data?.[0].host.risk.category_2_count}` - ); - - // Result row doesn't exist if alerts are the only category - expect(queryByTestId('risk-summary-result-count')).not.toBeInTheDocument(); - expect(queryByTestId('risk-summary-result-score')).not.toBeInTheDocument(); - }); - it('renders risk summary table with context and totals', () => { - mockUseUiSetting.mockReturnValue([true]); - const { getByTestId } = render( <TestProviders> <FlyoutRiskSummary diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx index b0d988eaeac1a..fee62f34dfdee 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx @@ -23,8 +23,7 @@ import { euiThemeVars } from '@kbn/ui-theme'; import dateMath from '@kbn/datemath'; import { i18n } from '@kbn/i18n'; import { ExpandablePanel } from '@kbn/security-solution-common'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../common/constants'; -import { useKibana, useUiSetting$ } from '../../../common/lib/kibana/kibana_react'; +import { useKibana } from '../../../common/lib/kibana/kibana_react'; import { EntityDetailsLeftPanelTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; @@ -82,17 +81,9 @@ const FlyoutRiskSummaryComponent = <T extends RiskScoreEntity>({ const xsFontSize = useEuiFontSize('xxs').fontSize; - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); + const columns = useMemo(() => buildColumns(), []); - const columns = useMemo( - () => buildColumns(isAssetCriticalityEnabled), - [isAssetCriticalityEnabled] - ); - - const rows = useMemo( - () => getItems(entityData, isAssetCriticalityEnabled), - [entityData, isAssetCriticalityEnabled] - ); + const rows = useMemo(() => getItems(entityData), [entityData]); const onToggle = useCallback( (isOpen: boolean) => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index 0e09e5ceac3ef..53abf222d39e4 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -31,8 +31,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useEntityEngineStatus } from '../components/entity_store/hooks/use_entity_engine_status'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; -import { useUiSetting$, useKibana } from '../../common/lib/kibana'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../common/constants'; +import { useKibana } from '../../common/lib/kibana'; import { AssetCriticalityFileUploader } from '../components/asset_criticality_file_uploader/asset_criticality_file_uploader'; import { useAssetCriticalityPrivileges } from '../components/asset_criticality/use_asset_criticality'; import { useHasSecurityCapability } from '../../helper_hooks'; @@ -50,7 +49,6 @@ const entityStoreInstallingStatuses = ['installing', 'loading']; export const EntityStoreManagementPage = () => { const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); const { data: assetCriticalityPrivileges, error: assetCriticalityPrivilegesError, @@ -110,10 +108,7 @@ export const EntityStoreManagementPage = () => { const errorMessage = assetCriticalityPrivilegesError?.body.message ?? ( <FormattedMessage id="xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage" - defaultMessage='Please enable "{ENABLE_ASSET_CRITICALITY_SETTING}" in advanced settings to access this functionality.' - values={{ - ENABLE_ASSET_CRITICALITY_SETTING, - }} + defaultMessage="The don't have privileges to access Asset Criticality feature. Contact your administrator for further assistance." /> ); @@ -218,7 +213,6 @@ export const EntityStoreManagementPage = () => { const FileUploadSection: React.FC = () => { if ( !hasEntityAnalyticsCapability || - !isAssetCriticalityEnabled || assetCriticalityPrivilegesError?.body.status_code === 403 ) { return <AssetCriticalityIssueCallout />; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx index 6f8e3ad587a0d..d4f2791f4a314 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx @@ -27,8 +27,7 @@ import { ENTITY_RISK_LEVEL } from '../../../../entity_analytics/components/risk_ export const getHostsColumns = ( showRiskColumn: boolean, - dispatchSeverityUpdate: (s: RiskSeverity) => void, - isAssetCriticalityEnabled: boolean + dispatchSeverityUpdate: (s: RiskSeverity) => void ): HostsTableColumns => { const columns: HostsTableColumns = [ { @@ -166,24 +165,22 @@ export const getHostsColumns = ( }); } - if (isAssetCriticalityEnabled) { - columns.push({ - field: 'node.criticality', - name: i18n.ASSET_CRITICALITY, - truncateText: false, - mobileOptions: { show: true }, - sortable: false, - render: (assetCriticality: CriticalityLevelWithUnassigned) => { - if (!assetCriticality) return getEmptyTagValue(); - return ( - <AssetCriticalityBadge - criticalityLevel={assetCriticality} - css={{ verticalAlign: 'middle' }} - /> - ); - }, - }); - } + columns.push({ + field: 'node.criticality', + name: i18n.ASSET_CRITICALITY, + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (assetCriticality: CriticalityLevelWithUnassigned) => { + if (!assetCriticality) return getEmptyTagValue(); + return ( + <AssetCriticalityBadge + criticalityLevel={assetCriticality} + css={{ verticalAlign: 'middle' }} + /> + ); + }, + }); return columns; }; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx index 8d20fed91a66a..606bd77ebcc45 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx @@ -180,31 +180,6 @@ describe('Hosts Table', () => { expect(queryByTestId('tableHeaderCell_node.criticality_5')).toBeInTheDocument(); }); - test('it does not render "Asset Criticality" column when Asset Criticality is not enabled in Kibana settings', () => { - mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true }); - mockUseHasSecurityCapability.mockReturnValue(true); - mockUseUiSetting.mockReturnValue([false]); - - const { queryByTestId } = render( - <TestProviders store={store}> - <HostsTable - id="hostsQuery" - isInspect={false} - loading={false} - data={mockData} - totalCount={0} - fakeTotalCount={-1} - setQuerySkip={jest.fn()} - showMorePagesIndicator={false} - loadPage={loadPage} - type={hostsModel.HostsType.page} - /> - </TestProviders> - ); - - expect(queryByTestId('tableHeaderCell_node.criticality_5')).not.toBeInTheDocument(); - }); - describe('Sorting on Table', () => { let wrapper: ReturnType<typeof mount>; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx index 20299d564d587..e7a9808461beb 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx @@ -9,7 +9,6 @@ import React, { useMemo, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import type { HostEcs, OsEcs } from '@kbn/securitysolution-ecs'; -import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import type { CriticalityLevelWithUnassigned } from '../../../../../common/entity_analytics/asset_criticality/types'; import { HostsFields } from '../../../../../common/api/search_strategy/hosts/model/sort'; import type { @@ -30,10 +29,7 @@ import type { HostsSortField, } from '../../../../../common/search_strategy/security_solution/hosts'; import type { Direction, RiskSeverity } from '../../../../../common/search_strategy'; -import { - ENABLE_ASSET_CRITICALITY_SETTING, - SecurityPageName, -} from '../../../../../common/constants'; +import { SecurityPageName } from '../../../../../common/constants'; import { HostsTableType } from '../../store/model'; import { useNavigateTo } from '../../../../common/lib/kibana/hooks'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; @@ -160,21 +156,13 @@ const HostsTableComponent: React.FC<HostsTableProps> = ({ [dispatch, navigateTo, type] ); - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); - const hostsColumns = useMemo( () => getHostsColumns( isPlatinumOrTrialLicense && hasEntityAnalyticsCapability, - dispatchSeverityUpdate, - isAssetCriticalityEnabled + dispatchSeverityUpdate ), - [ - dispatchSeverityUpdate, - isPlatinumOrTrialLicense, - hasEntityAnalyticsCapability, - isAssetCriticalityEnabled, - ] + [dispatchSeverityUpdate, isPlatinumOrTrialLicense, hasEntityAnalyticsCapability] ); const sorting = useMemo(() => getSorting(sortField, direction), [sortField, direction]); diff --git a/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.test.tsx b/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.test.tsx index da54aa8aa05c8..eb8ce33bf76ff 100644 --- a/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.test.tsx @@ -50,7 +50,7 @@ describe('Users Table Component', () => { ); expect(getByTestId('table-allUsers-loading-false')).toBeInTheDocument(); - expect(getAllByRole('columnheader').length).toBe(3); + expect(getAllByRole('columnheader').length).toBe(4); expect(getByText(userName)).toBeInTheDocument(); }); @@ -108,7 +108,7 @@ describe('Users Table Component', () => { </TestProviders> ); - expect(getAllByRole('columnheader').length).toBe(4); + expect(getAllByRole('columnheader').length).toBe(5); expect(getByText('Critical')).toBeInTheDocument(); }); @@ -142,7 +142,7 @@ describe('Users Table Component', () => { </TestProviders> ); - expect(getAllByRole('columnheader').length).toBe(3); + expect(getAllByRole('columnheader').length).toBe(4); expect(queryByText('Critical')).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx b/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx index 92303187f231a..dccb3d89f4f65 100644 --- a/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx @@ -9,7 +9,6 @@ import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { EuiLink, EuiText } from '@elastic/eui'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../../common/constants'; import { AssetCriticalityBadge } from '../../../../entity_analytics/components/asset_criticality'; import type { CriticalityLevelWithUnassigned } from '../../../../../common/entity_analytics/asset_criticality/types'; import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; @@ -40,7 +39,7 @@ import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml import { VIEW_USERS_BY_SEVERITY } from '../../../../entity_analytics/components/user_risk_score_table/translations'; import { SecurityPageName } from '../../../../app/types'; import { UsersTableType } from '../../store/model'; -import { useNavigateTo, useUiSetting$ } from '../../../../common/lib/kibana'; +import { useNavigateTo } from '../../../../common/lib/kibana'; const tableType = usersModel.UsersTableType.allUsers; @@ -78,8 +77,7 @@ const rowItems: ItemsPerRow[] = [ const getUsersColumns = ( showRiskColumn: boolean, - dispatchSeverityUpdate: (s: RiskSeverity) => void, - isAssetCriticalityEnabled: boolean + dispatchSeverityUpdate: (s: RiskSeverity) => void ): UsersTableColumns => { const columns: UsersTableColumns = [ { @@ -148,24 +146,22 @@ const getUsersColumns = ( }); } - if (isAssetCriticalityEnabled) { - columns.push({ - field: 'criticality', - name: i18n.ASSET_CRITICALITY, - truncateText: false, - mobileOptions: { show: true }, - sortable: false, - render: (assetCriticality: CriticalityLevelWithUnassigned) => { - if (!assetCriticality) return getEmptyTagValue(); - return ( - <AssetCriticalityBadge - criticalityLevel={assetCriticality} - css={{ verticalAlign: 'middle' }} - /> - ); - }, - }); - } + columns.push({ + field: 'criticality', + name: i18n.ASSET_CRITICALITY, + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (assetCriticality: CriticalityLevelWithUnassigned) => { + if (!assetCriticality) return getEmptyTagValue(); + return ( + <AssetCriticalityBadge + criticalityLevel={assetCriticality} + css={{ verticalAlign: 'middle' }} + /> + ); + }, + }); return columns; }; @@ -246,11 +242,9 @@ const UsersTableComponent: React.FC<UsersTableProps> = ({ [dispatch, navigateTo] ); - const [isAssetCriticalityEnabled] = useUiSetting$<boolean>(ENABLE_ASSET_CRITICALITY_SETTING); const columns = useMemo( - () => - getUsersColumns(isPlatinumOrTrialLicense, dispatchSeverityUpdate, isAssetCriticalityEnabled), - [isPlatinumOrTrialLicense, dispatchSeverityUpdate, isAssetCriticalityEnabled] + () => getUsersColumns(isPlatinumOrTrialLicense, dispatchSeverityUpdate), + [isPlatinumOrTrialLicense, dispatchSeverityUpdate] ); return ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.test.ts index e3967bd2e0040..415128d1d9f0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.test.ts @@ -15,7 +15,6 @@ import { createAlert } from './__mocks__/alerts'; import { isIndexExist } from './utils/is_index_exist'; import { allowedExperimentalValues } from '../../../../../../common'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../../../common/constants'; jest.mock('./search_enrichments', () => ({ searchEnrichments: jest.fn(), @@ -190,11 +189,6 @@ describe('enrichEvents', () => { // enable for asset criticality mockIsIndexExist.mockImplementation(() => true); - // enable asset criticality settings - alertServices.uiSettingsClient.get.mockImplementation((key) => - Promise.resolve(key === ENABLE_ASSET_CRITICALITY_SETTING) - ); - const enrichedEvents = await enrichEvents({ logger: ruleExecutionLogger, services: alertServices, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.ts index 0ed95e59e5542..7f0c797bc6743 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../../../common/constants'; import { createHostRiskEnrichments } from './enrichment_by_type/host_risk'; import { createUserRiskEnrichments } from './enrichment_by_type/user_risk'; @@ -22,10 +21,7 @@ import type { } from './types'; import { applyEnrichmentsToEvents } from './utils/transforms'; import { isIndexExist } from './utils/is_index_exist'; -import { - getHostRiskIndex, - getUserRiskIndex, -} from '../../../../../../common/search_strategy/security_solution/risk_score/common'; +import { getHostRiskIndex, getUserRiskIndex } from '../../../../../../common/search_strategy'; export const enrichEvents: EnrichEventsFunction = async ({ services, @@ -39,10 +35,6 @@ export const enrichEvents: EnrichEventsFunction = async ({ logger.debug('Alert enrichments started'); const isNewRiskScoreModuleAvailable = experimentalFeatures?.riskScoringRoutesEnabled ?? false; - const { uiSettingsClient } = services; - const isAssetCriticalityEnabled = await uiSettingsClient.get<boolean>( - ENABLE_ASSET_CRITICALITY_SETTING - ); let isNewRiskScoreModuleInstalled = false; if (isNewRiskScoreModuleAvailable) { @@ -87,29 +79,27 @@ export const enrichEvents: EnrichEventsFunction = async ({ ); } - if (isAssetCriticalityEnabled) { - const assetCriticalityIndexExist = await isIndexExist({ - services, - index: getAssetCriticalityIndex(spaceId), - }); - if (assetCriticalityIndexExist) { - enrichments.push( - createUserAssetCriticalityEnrichments({ - services, - logger, - events, - spaceId, - }) - ); - enrichments.push( - createHostAssetCriticalityEnrichments({ - services, - logger, - events, - spaceId, - }) - ); - } + const assetCriticalityIndexExist = await isIndexExist({ + services, + index: getAssetCriticalityIndex(spaceId), + }); + if (assetCriticalityIndexExist) { + enrichments.push( + createUserAssetCriticalityEnrichments({ + services, + logger, + events, + spaceId, + }) + ); + enrichments.push( + createHostAssetCriticalityEnrichments({ + services, + logger, + events, + spaceId, + }) + ); } const allEnrichmentsResults = await Promise.allSettled(enrichments); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts index 9de2d8c6bae2c..9822dfd1dad1f 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts @@ -9,7 +9,6 @@ import type { AssetCriticalityService } from './asset_criticality_service'; const buildMockAssetCriticalityService = (): jest.Mocked<AssetCriticalityService> => ({ getCriticalitiesByIdentifiers: jest.fn().mockResolvedValue([]), - isEnabled: jest.fn().mockReturnValue(true), }); export const assetCriticalityServiceMock = { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts index b67efbfa58e01..e56454499a00e 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts @@ -7,7 +7,6 @@ import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; import { isEmpty } from 'lodash/fp'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../common/constants'; import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics'; import type { AssetCriticalityDataClient } from './asset_criticality_data_client'; @@ -24,7 +23,6 @@ export interface AssetCriticalityService { getCriticalitiesByIdentifiers: ( identifiers: CriticalityIdentifier[] ) => Promise<AssetCriticalityRecord[]>; - isEnabled: () => Promise<boolean>; } const isCriticalityIdentifierValid = (identifier: CriticalityIdentifier): boolean => @@ -94,9 +92,7 @@ interface AssetCriticalityServiceFactoryOptions { export const assetCriticalityServiceFactory = ({ assetCriticalityDataClient, - uiSettingsClient, }: AssetCriticalityServiceFactoryOptions): AssetCriticalityService => ({ getCriticalitiesByIdentifiers: (identifiers: CriticalityIdentifier[]) => getCriticalitiesByIdentifiers({ assetCriticalityDataClient, identifiers }), - isEnabled: () => uiSettingsClient.get<boolean>(ENABLE_ASSET_CRITICALITY_SETTING), }); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts index 960f6c87be283..93251bcf92652 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts @@ -17,11 +17,9 @@ import type { ConfigType } from '../../../../config'; import { ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -72,7 +70,6 @@ export const assetCriticalityPublicBulkUploadRoute = ( const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts index 4e0692f631718..6c2437081500d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts @@ -13,11 +13,9 @@ import { DeleteAssetCriticalityRecordRequestQuery } from '../../../../../common/ import { ASSET_CRITICALITY_PUBLIC_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -62,7 +60,6 @@ export const assetCriticalityPublicDeleteRoute = ( const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts index ed63f6207fec1..048df61757a56 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts @@ -15,11 +15,9 @@ import { import { ASSET_CRITICALITY_PUBLIC_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -52,7 +50,6 @@ export const assetCriticalityPublicGetRoute = ( ): Promise<IKibanaResponse<GetAssetCriticalityRecordResponse>> => { const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const securitySolution = await context.securitySolution; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts index 64bbca127ed77..a6316646bc612 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts @@ -11,13 +11,11 @@ import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { ASSET_CRITICALITY_PUBLIC_LIST_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import type { FindAssetCriticalityRecordsResponse } from '../../../../../common/api/entity_analytics/asset_criticality'; import { FindAssetCriticalityRecordsRequestQuery } from '../../../../../common/api/entity_analytics/asset_criticality'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -50,7 +48,6 @@ export const assetCriticalityPublicListRoute = ( ): Promise<IKibanaResponse<FindAssetCriticalityRecordsResponse>> => { const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const securitySolution = await context.securitySolution; const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts index 7f6b80dd92909..8c40335423973 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts @@ -11,12 +11,10 @@ import type { AssetCriticalityGetPrivilegesResponse } from '../../../../../commo import { ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import { getUserAssetCriticalityPrivileges } from '../get_user_asset_criticality_privileges'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -46,8 +44,6 @@ export const assetCriticalityInternalPrivilegesRoute = ( ): Promise<IKibanaResponse<AssetCriticalityGetPrivilegesResponse>> => { const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); const [_, { security }] = await getStartServices(); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts index a0070503a3f8c..fc1cc92bbe1cf 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts @@ -11,12 +11,10 @@ import type { GetAssetCriticalityStatusResponse } from '../../../../../common/ap import { ASSET_CRITICALITY_INTERNAL_STATUS_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; import type { EntityAnalyticsRoutesDeps } from '../../types'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import { AssetCriticalityAuditActions } from '../audit'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; @@ -41,7 +39,6 @@ export const assetCriticalityInternalStatusRoute = ( ): Promise<IKibanaResponse<GetAssetCriticalityStatusResponse>> => { const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const securitySolution = await context.securitySolution; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts index cbe434ccb25cf..8c1d94176111c 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts @@ -16,13 +16,11 @@ import type { HapiReadableStream } from '../../../../types'; import { ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import { transformCSVToUpsertRecords } from '../transform_csv_to_upsert_records'; import { createAssetCriticalityProcessedFileEvent } from '../../../telemetry/event_based/events'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -82,7 +80,6 @@ export const assetCriticalityPublicCSVUploadRoute = ( const telemetry = coreStart.analytics; try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); const fileStream = request.body.file as HapiReadableStream; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts index cab348e7c5518..488a75c0196ab 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts @@ -16,14 +16,12 @@ import { import { ASSET_CRITICALITY_PUBLIC_URL, APP_ID, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; export const assetCriticalityPublicUpsertRoute = ( router: EntityAnalyticsRoutesDeps['router'], @@ -53,7 +51,6 @@ export const assetCriticalityPublicUpsertRoute = ( ): Promise<IKibanaResponse<CreateAssetCriticalityRecordResponse>> => { const siemResponse = buildSiemResponse(response); try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); await checkAndInitAssetCriticalityResources(context, logger); const securitySolution = await context.securitySolution; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts index 45ad1241fda33..ff1062393c935 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts @@ -175,13 +175,6 @@ const processScores = async ({ return []; } - const isAssetCriticalityEnabled = await assetCriticalityService.isEnabled(); - if (!isAssetCriticalityEnabled) { - return buckets.map((bucket) => - formatForResponse({ bucket, now, identifierField, includeNewFields: false }) - ); - } - const identifiers = buckets.map((bucket) => ({ id_field: identifierField, id_value: bucket.key[identifierField], diff --git a/x-pack/plugins/security_solution/server/ui_settings.ts b/x-pack/plugins/security_solution/server/ui_settings.ts index ecf3629b54831..842b8bbeceff8 100644 --- a/x-pack/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/plugins/security_solution/server/ui_settings.ts @@ -40,7 +40,6 @@ import { DEFAULT_ALERT_TAGS_VALUE, EXCLUDE_COLD_AND_FROZEN_TIERS_IN_ANALYZER, EXCLUDED_DATA_TIERS_FOR_RULE_EXECUTION, - ENABLE_ASSET_CRITICALITY_SETTING, ENABLE_VISUALIZATIONS_IN_FLYOUT_SETTING, } from '../common/constants'; import type { ExperimentalFeatures } from '../common/experimental_features'; @@ -180,24 +179,6 @@ export const initUiSettings = ( requiresPageReload: true, schema: schema.boolean(), }, - [ENABLE_ASSET_CRITICALITY_SETTING]: { - name: i18n.translate('xpack.securitySolution.uiSettings.enableAssetCriticalityTitle', { - defaultMessage: 'Asset Criticality', - }), - value: false, - description: i18n.translate( - 'xpack.securitySolution.uiSettings.enableAssetCriticalityDescription', - { - defaultMessage: - '<p>Enables asset criticality assignment workflows and its contributions to entity risk </p>', - values: { p: (chunks) => `<p>${chunks}</p>` }, - } - ), - type: 'boolean', - category: [APP_ID], - requiresPageReload: true, - schema: schema.boolean(), - }, [EXCLUDE_COLD_AND_FROZEN_TIERS_IN_ANALYZER]: { name: i18n.translate( 'xpack.securitySolution.uiSettings.excludeColdAndFrozenTiersInAnalyzer', diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 281807b6db45f..d2c35721fdddb 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -40594,8 +40594,6 @@ "xpack.securitySolution.uiSettings.defaultThreatIndexLabel": "Index de menaces", "xpack.securitySolution.uiSettings.defaultTimeRangeDescription": "<p>Période de temps par défaut dans le filtre de temps Security.</p>", "xpack.securitySolution.uiSettings.defaultTimeRangeLabel": "Période du filtre de temps", - "xpack.securitySolution.uiSettings.enableAssetCriticalityDescription": "<p>Permet des flux de travail pour l'affectation de l'état critique des actifs et ses contributions au risque de l'entité </p>", - "xpack.securitySolution.uiSettings.enableAssetCriticalityTitle": "Criticité des ressources", "xpack.securitySolution.uiSettings.enableCcsReadWarningLabel": "Avertissement lié aux privilèges de la règle CCS", "xpack.securitySolution.uiSettings.enableCcsWarningDescription": "<p>Active les avertissements de vérification des privilèges dans les règles relatives aux index CCS</p>", "xpack.securitySolution.uiSettings.enableNewsFeedDescription": "<p>Active le fil d'actualités</p>", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 19d3dfb274fa2..bc4f0b3f6cf1a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -40340,8 +40340,6 @@ "xpack.securitySolution.uiSettings.defaultThreatIndexLabel": "脅威インデックス", "xpack.securitySolution.uiSettings.defaultTimeRangeDescription": "<p>セキュリティ時間フィルダーのデフォルトの期間です。</p>", "xpack.securitySolution.uiSettings.defaultTimeRangeLabel": "時間フィルターの期間", - "xpack.securitySolution.uiSettings.enableAssetCriticalityDescription": "<p>アセット重要度割り当てワークフローとエンティティリスクへの寄与を有効化します </p>", - "xpack.securitySolution.uiSettings.enableAssetCriticalityTitle": "アセット重要度", "xpack.securitySolution.uiSettings.enableCcsReadWarningLabel": "CCSルール権限警告", "xpack.securitySolution.uiSettings.enableCcsWarningDescription": "<p>CCSインデックスのルールで権限チェック警告を有効にします</p>", "xpack.securitySolution.uiSettings.enableNewsFeedDescription": "<p>ニュースフィードを有効にします</p>", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d9e89fe098903..e018909babf64 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -40385,8 +40385,6 @@ "xpack.securitySolution.uiSettings.defaultThreatIndexLabel": "威胁索引", "xpack.securitySolution.uiSettings.defaultTimeRangeDescription": "<p>Security 时间筛选中的默认时段。</p>", "xpack.securitySolution.uiSettings.defaultTimeRangeLabel": "时间筛选时段", - "xpack.securitySolution.uiSettings.enableAssetCriticalityDescription": "<p>启用资产关键度分配工作流及其对实体风险的贡献率 </p>", - "xpack.securitySolution.uiSettings.enableAssetCriticalityTitle": "资产关键度", "xpack.securitySolution.uiSettings.enableCcsReadWarningLabel": "CCS 规则权限警告", "xpack.securitySolution.uiSettings.enableCcsWarningDescription": "<p>在规则中为 CCS 索引启用权限检查警告</p>", "xpack.securitySolution.uiSettings.enableNewsFeedDescription": "<p>启用新闻源</p>", diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts index 8c1462a84a971..5828a29eccb54 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts @@ -45,7 +45,6 @@ import { DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL, - ENABLE_ASSET_CRITICALITY_SETTING, } from '@kbn/security-solution-plugin/common/constants'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils'; @@ -95,7 +94,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const esDeleteAllIndices = getService('esDeleteAllIndices'); // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); @@ -334,9 +332,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts index e2d39bce4b024..9515924213ce6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts @@ -35,10 +35,7 @@ import { ALERT_GROUP_ID, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; -import { - DETECTION_ENGINE_RULES_URL, - ENABLE_ASSET_CRITICALITY_SETTING, -} from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { getEqlRuleForAlertTesting, getAlerts, @@ -72,7 +69,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); // TODO: add a new service for loading archiver files similar to "getService('es')" @@ -774,9 +770,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts index 26764650287fc..0c3069b3c3b62 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts @@ -19,10 +19,7 @@ import { TIMESTAMP, ALERT_START, } from '@kbn/rule-data-utils'; -import { - DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL, - ENABLE_ASSET_CRITICALITY_SETTING, -} from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; import { getSuppressionMaxSignalsWarning as getSuppressionMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; @@ -1702,14 +1699,9 @@ export default ({ getService }: FtrProviderContext) => { }); describe('alert enrichment', () => { - const kibanaServer = getService('kibanaServer'); - before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/entity/risks'); await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts index d44896115fae3..723a2a7d2dfa3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts @@ -14,7 +14,6 @@ import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/comm import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { getPreviewAlerts, previewRule, @@ -40,7 +39,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const utils = getService('securitySolutionUtils'); const { indexEnhancedDocuments, indexListOfDocuments, indexGeneratedDocuments } = @@ -916,9 +914,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts index 2d4618a431599..24685cc137f0e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts @@ -25,7 +25,6 @@ import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_ import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; import { getSuppressionMaxSignalsWarning as getSuppressionMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { getPreviewAlerts, previewRule, @@ -48,7 +47,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const { indexEnhancedDocuments, indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ es, @@ -2070,9 +2068,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts index 663da2aef5784..5b7f79615d635 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts @@ -41,7 +41,6 @@ import { } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { previewRule, getAlerts, @@ -186,7 +185,6 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); @@ -1655,9 +1653,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts index a6ac2fa6b139e..1ecf949b18951 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts @@ -21,7 +21,6 @@ import { import { getSuppressionMaxSignalsWarning as getSuppressionMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; @@ -44,7 +43,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const { indexListOfDocuments: indexListOfSourceDocuments, @@ -2568,9 +2566,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts index 1418d6953177e..2d63847ca0db7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts @@ -29,10 +29,7 @@ import { } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { expect } from 'expect'; -import { - DETECTION_ENGINE_RULES_URL, - ENABLE_ASSET_CRITICALITY_SETTING, -} from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { createListsIndex, deleteAllExceptions, @@ -63,7 +60,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const request = supertestLib(url.format(config.get('servers.kibana'))); @@ -331,9 +327,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts index 39a7138451f34..8ebcafcdc46b5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts @@ -22,10 +22,7 @@ import { TIMESTAMP, } from '@kbn/rule-data-utils'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { - DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL, - ENABLE_ASSET_CRITICALITY_SETTING, -} from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { @@ -1102,14 +1099,9 @@ export default ({ getService }: FtrProviderContext) => { }); describe('with enrichments', () => { - const kibanaServer = getService('kibanaServer'); - before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/entity/risks'); await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts index a1695ec04021c..970d6ab3ba6ed 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts @@ -14,7 +14,7 @@ import { orderBy } from 'lodash'; import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; + import { getAlerts, getPreviewAlerts, @@ -43,7 +43,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const { indexEnhancedDocuments } = dataGeneratorFactory({ es, index: 'new_terms', @@ -1067,9 +1066,6 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts index 285bb81c6ac93..41d88869cdf45 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts @@ -18,7 +18,6 @@ import { TIMESTAMP, ALERT_START, } from '@kbn/rule-data-utils'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { getSuppressionMaxSignalsWarning as getSuppressionMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -2250,15 +2249,11 @@ export default ({ getService }: FtrProviderContext) => { const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); const path = dataPathBuilder.getPath('auditbeat/hosts'); - const kibanaServer = getService('kibanaServer'); before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/entity/risks'); await esArchiver.load(path); await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts index e0e93ba8ed300..2f7086664fbcb 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts @@ -27,7 +27,6 @@ import { ALERT_THRESHOLD_RESULT, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { createRule, deleteAllRules, @@ -51,7 +50,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); @@ -447,9 +445,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts index 52cf49b711394..ecc97d8615f3f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts @@ -21,7 +21,6 @@ import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_U import { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { AlertSuppression } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema'; @@ -44,7 +43,6 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); @@ -994,9 +992,6 @@ export default ({ getService }: FtrProviderContext) => { describe('with asset criticality', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); }); after(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts index 976bfaa8f1113..bc5eccd168418 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts @@ -19,8 +19,6 @@ import { assetCriticalityRouteHelpersFactory, getAssetCriticalityDoc, getAssetCriticalityIndex, - enableAssetCriticalityAdvancedSetting, - disableAssetCriticalityAdvancedSetting, createAssetCriticalityRecords, riskEngineRouteHelpersFactory, } from '../../utils'; @@ -28,7 +26,6 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const es = getService('es'); - const kibanaServer = getService('kibanaServer'); const log = getService('log'); const supertest = getService('supertest'); const assetCriticalityRoutes = assetCriticalityRouteHelpersFactory(supertest); @@ -41,14 +38,6 @@ export default ({ getService }: FtrProviderContext) => { await cleanAssetCriticality({ log, es }); }); - after(async () => { - await disableAssetCriticalityAdvancedSetting(kibanaServer, log); - }); - - beforeEach(async () => { - await enableAssetCriticalityAdvancedSetting(kibanaServer, log); - }); - afterEach(async () => { await riskEngineRoutes.cleanUp(); await cleanAssetCriticality({ log, es }); @@ -181,20 +170,6 @@ export default ({ getService }: FtrProviderContext) => { expectStatusCode: 400, }); }); - - it('should return 403 if the advanced setting is disabled', async () => { - await disableAssetCriticalityAdvancedSetting(kibanaServer, log); - - const validAssetCriticality = { - id_field: 'host.name', - id_value: 'host-01', - criticality_level: 'high_impact', - }; - - await assetCriticalityRoutes.upsert(validAssetCriticality, { - expectStatusCode: 403, - }); - }); }); describe('get', () => { @@ -220,14 +195,6 @@ export default ({ getService }: FtrProviderContext) => { expectStatusCode: 400, }); }); - - it('should return 403 if the advanced setting is disabled', async () => { - await disableAssetCriticalityAdvancedSetting(kibanaServer, log); - - await assetCriticalityRoutes.get('host.name', 'doesnt-matter', { - expectStatusCode: 403, - }); - }); }); describe('list', () => { @@ -424,20 +391,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - it('should return a 403 if the advanced setting is disabled', async () => { - await disableAssetCriticalityAdvancedSetting(kibanaServer, log); - - const validRecord: CreateAssetCriticalityRecord = { - id_field: 'host.name', - id_value: 'delete-me', - criticality_level: 'high_impact', - }; - - await assetCriticalityRoutes.bulkUpload([validRecord], { - expectStatusCode: 403, - }); - }); - it('should correctly upload a valid record for one entity', async () => { const validRecord: CreateAssetCriticalityRecord = { id_field: 'host.name', @@ -533,14 +486,6 @@ export default ({ getService }: FtrProviderContext) => { expect(res.body.deleted).to.eql(false); expect(res.body.record).to.eql(undefined); }); - - it('should return 403 if the advanced setting is disabled', async () => { - await disableAssetCriticalityAdvancedSetting(kibanaServer, log); - - await assetCriticalityRoutes.delete('host.name', 'doesnt-matter', { - expectStatusCode: 403, - }); - }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts index 28a42d02bdaec..496cde9a79e13 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts @@ -8,8 +8,6 @@ import expect from 'expect'; import { assetCriticalityRouteHelpersFactory, cleanAssetCriticality, - disableAssetCriticalityAdvancedSetting, - enableAssetCriticalityAdvancedSetting, getAssetCriticalityDoc, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -18,7 +16,6 @@ export default ({ getService }: FtrProviderContext) => { const esClient = getService('es'); const supertest = getService('supertest'); const assetCriticalityRoutes = assetCriticalityRouteHelpersFactory(supertest); - const kibanaServer = getService('kibanaServer'); const log = getService('log'); const expectAssetCriticalityDocMatching = async (expectedDoc: { id_field: string; @@ -37,10 +34,6 @@ export default ({ getService }: FtrProviderContext) => { await cleanAssetCriticality({ es: esClient, namespace: 'default', log }); }); - beforeEach(async () => { - await enableAssetCriticalityAdvancedSetting(kibanaServer, log); - }); - after(async () => { await cleanAssetCriticality({ es: esClient, namespace: 'default', log }); }); @@ -188,13 +181,5 @@ export default ({ getService }: FtrProviderContext) => { failed: 0, }); }); - - it('should return 403 if the advanced setting is disabled', async () => { - await disableAssetCriticalityAdvancedSetting(kibanaServer, log); - - await assetCriticalityRoutes.uploadCsv('host,host-1,low_impact', { - expectStatusCode: 403, - }); - }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_privileges.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_privileges.ts index 0c187d3f45cc0..7b35787cafe24 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_privileges.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_privileges.ts @@ -6,10 +6,7 @@ */ import expect from '@kbn/expect'; import { ROLES as SERVERLESS_USERNAMES } from '@kbn/security-solution-plugin/common/test'; -import { - assetCriticalityRouteHelpersFactoryNoAuth, - enableAssetCriticalityAdvancedSetting, -} from '../../utils'; +import { assetCriticalityRouteHelpersFactoryNoAuth } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { usersAndRolesFactory } from '../../utils/users_and_roles'; @@ -67,9 +64,6 @@ const USERNAME_TO_ROLES = { }; export default ({ getService }: FtrProviderContext) => { - const kibanaServer = getService('kibanaServer'); - const log = getService('log'); - describe('Entity Analytics - Asset Criticality Privileges API', () => { describe('@ess Asset Criticality Privileges API', () => { const supertestWithoutAuth = getService('supertestWithoutAuth'); @@ -95,7 +89,6 @@ export default ({ getService }: FtrProviderContext) => { }); before(async () => { await createPrivilegeTestUsers(); - await enableAssetCriticalityAdvancedSetting(kibanaServer, log); }); describe('Asset Criticality privileges API', () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts index 76baaec707db0..fb50a9beeed90 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts @@ -23,7 +23,6 @@ import { cleanAssetCriticality, waitForAssetCriticalityToBePresent, riskEngineRouteHelpersFactory, - enableAssetCriticalityAdvancedSetting, sanitizeScores, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -34,7 +33,6 @@ export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const riskEngineRoutes = riskEngineRouteHelpersFactory(supertest); @@ -77,9 +75,6 @@ export default ({ getService }: FtrProviderContext): void => { describe('@ess @serverless @serverlessQA Risk Scoring Entity Calculation API', function () { this.tags(['esGate']); - before(async () => { - await enableAssetCriticalityAdvancedSetting(kibanaServer, log); - }); context('with auditbeat data', () => { const { indexListOfDocuments } = dataGeneratorFactory({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts index fbe2ca5cfe210..af4567eac3d6d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts @@ -23,7 +23,6 @@ import { cleanAssetCriticality, createAndSyncRuleAndAlertsFactory, deleteAllRiskScores, - enableAssetCriticalityAdvancedSetting, sanitizeScores, waitForAssetCriticalityToBePresent, } from '../../utils'; @@ -35,7 +34,6 @@ export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - const kibanaServer = getService('kibanaServer'); const createAndSyncRuleAndAlerts = createAndSyncRuleAndAlertsFactory({ supertest, log }); const previewRiskScores = async ({ @@ -70,10 +68,6 @@ export default ({ getService }: FtrProviderContext): void => { }; describe('@ess @serverless Risk Scoring Preview API', () => { - before(async () => { - await enableAssetCriticalityAdvancedSetting(kibanaServer, log); - }); - context('with auditbeat data', () => { const { indexListOfDocuments } = dataGeneratorFactory({ es, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts index 4c541d48b436b..690e8f99b4611 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts @@ -16,7 +16,6 @@ import { ASSET_CRITICALITY_PUBLIC_LIST_URL, ASSET_CRITICALITY_INTERNAL_STATUS_URL, ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, - ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, } from '@kbn/security-solution-plugin/common/constants'; @@ -28,51 +27,12 @@ import type { import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import querystring from 'querystring'; -import { KbnClient } from '@kbn/test'; import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { routeWithNamespace, waitFor } from '../../../../common/utils/security_solution'; export const getAssetCriticalityIndex = (namespace?: string) => `.asset-criticality.asset-criticality-${namespace ?? 'default'}`; -export const enableAssetCriticalityAdvancedSetting = async ( - kibanaServer: KbnClient, - log: ToolingLog -) => { - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: true, - }); - - // and wait for the setting to be applied - await waitFor( - async () => { - const setting = await kibanaServer.uiSettings.get(ENABLE_ASSET_CRITICALITY_SETTING); - return setting === true; - }, - 'disableAssetCriticalityAdvancedSetting', - log - ); -}; - -export const disableAssetCriticalityAdvancedSetting = async ( - kibanaServer: KbnClient, - log: ToolingLog -) => { - await kibanaServer.uiSettings.update({ - [ENABLE_ASSET_CRITICALITY_SETTING]: false, - }); - - // and wait for the setting to be applied - await waitFor( - async () => { - const setting = await kibanaServer.uiSettings.get(ENABLE_ASSET_CRITICALITY_SETTING); - return setting === false; - }, - 'disableAssetCriticalityAdvancedSetting', - log - ); -}; - export const cleanAssetCriticality = async ({ log, es, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts index 1a48a7835f195..016161b231a37 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts @@ -12,7 +12,6 @@ import { RESULT_STEP, VALID_LINES_MESSAGE, } from '../../screens/asset_criticality'; -import { enableAssetCriticality } from '../../tasks/api_calls/kibana_advanced_settings'; import { clickAssignButton, uploadAssetCriticalityFile } from '../../tasks/asset_criticality'; import { login } from '../../tasks/login'; import { visit } from '../../tasks/navigation'; @@ -26,7 +25,6 @@ describe( () => { beforeEach(() => { login(); - enableAssetCriticality(); visit(ENTITY_ANALYTICS_ASSET_CRITICALITY_URL); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts index 976e68ba1bbc1..a65d7ddb6371a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts @@ -42,7 +42,6 @@ import { ENTRA_DOCUMENT_TAB, OKTA_DOCUMENT_TAB, } from '../../screens/users/flyout_asset_panel'; -import { enableAssetCriticality } from '../../tasks/api_calls/kibana_advanced_settings'; const USER_NAME = 'user1'; const SIEM_KIBANA_HOST_NAME = 'Host-fwarau82er'; @@ -66,7 +65,6 @@ describe( cy.task('esArchiverLoad', { archiveName: 'risk_scores_new_complete_data' }); cy.task('esArchiverLoad', { archiveName: 'query_alert', useCreate: true, docsOnly: true }); cy.task('esArchiverLoad', { archiveName: 'user_managed_data' }); - enableAssetCriticality(); mockRiskEngineEnabled(); login(); visitWithTimeRange(ALERTS_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts index 7307fa2418b68..09735e45ee4e4 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts @@ -6,7 +6,6 @@ */ import { SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID } from '@kbn/management-settings-ids'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { rootRequest } from './common'; export const setKibanaSetting = (key: string, value: boolean | number | string) => { @@ -24,7 +23,3 @@ export const enableRelatedIntegrations = () => { export const disableRelatedIntegrations = () => { setKibanaSetting(SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID, false); }; - -export const enableAssetCriticality = () => { - setKibanaSetting(ENABLE_ASSET_CRITICALITY_SETTING, true); -}; From ad2ac714fc5c34e95f6eb133cc222b609a1f3a99 Mon Sep 17 00:00:00 2001 From: Jon <jon@elastic.co> Date: Tue, 15 Oct 2024 20:48:21 -0500 Subject: [PATCH 79/84] Reapply "[Security Solution] [Attack discovery] Output chunking / refinement, LangGraph migration, and evaluation improvements (#195669)" (#196440) #195669 + #196381 This reverts commit dbe6d82584c99fb8eda7fa117e220a97cfb0c33b. --------- Co-authored-by: Alex Szabo <alex.szabo@elastic.co> --- .../index.test.ts} | 2 +- .../index.ts} | 7 +- .../get_raw_data_or_default/index.test.ts | 28 + .../helpers/get_raw_data_or_default/index.ts | 13 + .../helpers/is_raw_data_valid/index.test.ts | 51 + .../alerts/helpers/is_raw_data_valid/index.ts | 11 + .../size_is_out_of_range/index.test.ts | 47 + .../helpers/size_is_out_of_range/index.ts | 12 + .../impl/alerts/helpers/types.ts | 14 + .../attack_discovery/common_attributes.gen.ts | 4 +- .../common_attributes.schema.yaml | 2 - .../evaluation/post_evaluate_route.gen.ts | 2 + .../post_evaluate_route.schema.yaml | 4 + .../kbn-elastic-assistant-common/index.ts | 16 + .../alerts_settings/alerts_settings.tsx | 3 +- .../alerts_settings_management.tsx | 1 + .../evaluation_settings.tsx | 64 +- .../evaluation_settings/translations.ts | 30 + .../impl/assistant_context/constants.tsx | 5 + .../impl/assistant_context/index.tsx | 5 +- .../impl/knowledge_base/alerts_range.tsx | 64 +- .../packages/kbn-elastic-assistant/index.ts | 20 + x-pack/plugins/elastic_assistant/README.md | 10 +- .../docs/img/default_assistant_graph.png | Bin 30104 -> 29798 bytes .../img/default_attack_discovery_graph.png | Bin 0 -> 22551 bytes .../scripts/draw_graph_script.ts | 46 +- .../__mocks__/attack_discovery_schema.mock.ts | 2 +- .../server/__mocks__/data_clients.mock.ts | 2 +- .../server/__mocks__/request_context.ts | 2 +- .../server/__mocks__/response.ts | 2 +- .../server/ai_assistant_service/index.ts | 4 +- .../evaluation/__mocks__/mock_examples.ts | 55 + .../evaluation/__mocks__/mock_runs.ts | 53 + .../attack_discovery/evaluation/constants.ts | 911 +++++++++++ .../evaluation/example_input/index.test.ts | 75 + .../evaluation/example_input/index.ts | 52 + .../get_default_prompt_template/index.test.ts | 42 + .../get_default_prompt_template/index.ts | 33 + .../index.test.ts | 125 ++ .../index.ts | 29 + .../index.test.ts | 117 ++ .../index.ts | 27 + .../get_custom_evaluator/index.test.ts | 98 ++ .../helpers/get_custom_evaluator/index.ts | 69 + .../index.test.ts | 79 + .../index.ts | 39 + .../helpers/get_evaluator_llm/index.test.ts | 161 ++ .../helpers/get_evaluator_llm/index.ts | 65 + .../get_graph_input_overrides/index.test.ts | 121 ++ .../get_graph_input_overrides/index.ts | 29 + .../lib/attack_discovery/evaluation/index.ts | 122 ++ .../evaluation/run_evaluations/index.ts | 113 ++ .../constants.ts | 21 + .../index.test.ts | 22 + .../get_generate_or_end_decision/index.ts | 9 + .../edges/generate_or_end/index.test.ts | 72 + .../edges/generate_or_end/index.ts | 38 + .../index.test.ts | 43 + .../index.ts | 28 + .../helpers/get_should_end/index.test.ts | 60 + .../helpers/get_should_end/index.ts | 16 + .../generate_or_refine_or_end/index.test.ts | 118 ++ .../edges/generate_or_refine_or_end/index.ts | 66 + .../edges/helpers/get_has_results/index.ts | 11 + .../helpers/get_has_zero_alerts/index.ts | 12 + .../get_refine_or_end_decision/index.ts | 25 + .../helpers/get_should_end/index.ts | 16 + .../edges/refine_or_end/index.ts | 61 + .../get_retrieve_or_generate/index.ts | 13 + .../index.ts | 36 + .../index.ts | 14 + .../helpers/get_max_retries_reached/index.ts | 14 + .../default_attack_discovery_graph/index.ts | 122 ++ .../mock/mock_anonymization_fields.ts | 0 ...en_and_acknowledged_alerts_qery_results.ts | 25 + ...n_and_acknowledged_alerts_query_results.ts | 1396 +++++++++++++++++ .../discard_previous_generations/index.ts | 30 + .../get_alerts_context_prompt/index.test.ts} | 17 +- .../get_alerts_context_prompt/index.ts | 22 + .../get_anonymized_alerts_from_state/index.ts | 11 + .../get_use_unrefined_results/index.ts | 27 + .../nodes/generate/index.ts | 154 ++ .../nodes/generate/schema/index.ts | 84 + .../index.ts | 20 + .../nodes/helpers/extract_json/index.test.ts | 67 + .../nodes/helpers/extract_json/index.ts | 17 + .../generations_are_repeating/index.test.tsx | 90 ++ .../generations_are_repeating/index.tsx | 25 + .../index.ts | 34 + .../nodes/helpers/get_combined/index.ts | 14 + .../index.ts | 43 + .../helpers/get_continue_prompt/index.ts | 15 + .../index.ts | 9 + .../helpers/get_output_parser/index.test.ts | 31 + .../nodes/helpers/get_output_parser/index.ts | 13 + .../helpers/parse_combined_or_throw/index.ts | 53 + .../helpers/response_is_hallucinated/index.ts | 9 + .../discard_previous_refinements/index.ts | 30 + .../get_combined_refine_prompt/index.ts | 48 + .../get_default_refine_prompt/index.ts | 11 + .../get_use_unrefined_results/index.ts | 17 + .../nodes/refine/index.ts | 166 ++ .../anonymized_alerts_retriever/index.ts | 74 + .../get_anonymized_alerts/index.test.ts} | 18 +- .../helpers/get_anonymized_alerts/index.ts} | 14 +- .../nodes/retriever/index.ts | 70 + .../state/index.ts | 86 + .../default_attack_discovery_graph/types.ts | 28 + .../create_attack_discovery.test.ts | 4 +- .../create_attack_discovery.ts | 4 +- .../field_maps_configuration.ts | 0 .../find_all_attack_discoveries.ts | 4 +- ...d_attack_discovery_by_connector_id.test.ts | 2 +- .../find_attack_discovery_by_connector_id.ts | 4 +- .../get_attack_discovery.test.ts | 2 +- .../get_attack_discovery.ts | 4 +- .../attack_discovery/persistence}/index.ts | 15 +- .../persistence/transforms}/transforms.ts | 2 +- .../attack_discovery/persistence}/types.ts | 4 +- .../update_attack_discovery.test.ts | 4 +- .../update_attack_discovery.ts | 6 +- .../server/lib/langchain/graphs/index.ts | 35 +- .../{ => get}/get_attack_discovery.test.ts | 25 +- .../{ => get}/get_attack_discovery.ts | 8 +- .../routes/attack_discovery/helpers.test.ts | 805 ---------- .../attack_discovery/helpers/helpers.test.ts | 273 ++++ .../attack_discovery/{ => helpers}/helpers.ts | 231 +-- .../cancel}/cancel_attack_discovery.test.ts | 24 +- .../cancel}/cancel_attack_discovery.ts | 10 +- .../post/helpers/handle_graph_error/index.tsx | 73 + .../invoke_attack_discovery_graph/index.tsx | 127 ++ .../helpers/request_is_valid/index.test.tsx | 87 + .../post/helpers/request_is_valid/index.tsx | 33 + .../throw_if_error_counts_exceeded/index.ts | 44 + .../translations.ts | 28 + .../{ => post}/post_attack_discovery.test.ts | 40 +- .../{ => post}/post_attack_discovery.ts | 80 +- .../evaluate/get_graphs_from_names/index.ts | 35 + .../server/routes/evaluate/post_evaluate.ts | 43 +- .../server/routes/evaluate/utils.ts | 2 +- .../elastic_assistant/server/routes/index.ts | 4 +- .../server/routes/register_routes.ts | 6 +- .../plugins/elastic_assistant/server/types.ts | 4 +- .../actionable_summary/index.tsx | 43 +- .../attack_discovery_panel/index.tsx | 11 +- .../attack_discovery_panel/title/index.tsx | 27 +- .../get_attack_discovery_markdown.ts | 2 +- .../attack_discovery/hooks/use_poll_api.tsx | 6 +- .../empty_prompt/animated_counter/index.tsx | 2 +- .../pages/empty_prompt/index.test.tsx | 72 +- .../pages/empty_prompt/index.tsx | 29 +- .../helpers/show_empty_states/index.ts | 36 + .../pages/empty_states/index.test.tsx | 33 +- .../pages/empty_states/index.tsx | 44 +- .../attack_discovery/pages/failure/index.tsx | 48 +- .../pages/failure/translations.ts | 13 +- .../attack_discovery/pages/generate/index.tsx | 36 + .../pages/header/index.test.tsx | 13 + .../attack_discovery/pages/header/index.tsx | 16 +- .../settings_modal/alerts_settings/index.tsx | 77 + .../header/settings_modal/footer/index.tsx | 57 + .../pages/header/settings_modal/index.tsx | 160 ++ .../settings_modal/is_tour_enabled/index.ts | 18 + .../header/settings_modal/translations.ts | 81 + .../attack_discovery/pages/helpers.test.ts | 4 + .../public/attack_discovery/pages/helpers.ts | 31 +- .../public/attack_discovery/pages/index.tsx | 104 +- .../pages/loading_callout/index.test.tsx | 3 +- .../pages/loading_callout/index.tsx | 13 +- .../get_loading_callout_alerts_count/index.ts | 24 + .../loading_messages/index.test.tsx | 4 +- .../loading_messages/index.tsx | 16 +- .../pages/no_alerts/index.test.tsx | 2 +- .../pages/no_alerts/index.tsx | 17 +- .../attack_discovery/pages/results/index.tsx | 112 ++ .../use_attack_discovery/helpers.test.ts | 25 +- .../use_attack_discovery/helpers.ts | 11 +- .../use_attack_discovery/index.test.tsx | 33 +- .../use_attack_discovery/index.tsx | 17 +- .../attack_discovery_tool.test.ts | 340 ---- .../attack_discovery/attack_discovery_tool.ts | 115 -- .../get_attack_discovery_prompt.ts | 20 - .../get_output_parser.test.ts | 31 - .../attack_discovery/get_output_parser.ts | 80 - .../server/assistant/tools/index.ts | 2 - .../helpers.test.ts | 117 -- .../open_and_acknowledged_alerts/helpers.ts | 22 - .../open_and_acknowledged_alerts_tool.test.ts | 3 +- .../open_and_acknowledged_alerts_tool.ts | 10 +- .../plugins/security_solution/tsconfig.json | 1 - 190 files changed, 8378 insertions(+), 2148 deletions(-) rename x-pack/{plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts => packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts} (96%) rename x-pack/{plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts => packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts} (87%) create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts create mode 100644 x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts rename x-pack/plugins/{security_solution/server/assistant/tools => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph}/mock/mock_anonymization_fields.ts (100%) create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts rename x-pack/plugins/{security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts} (70%) create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts rename x-pack/plugins/{security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts} (90%) rename x-pack/plugins/{security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts => elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts} (77%) create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/create_attack_discovery}/create_attack_discovery.test.ts (94%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/create_attack_discovery}/create_attack_discovery.ts (95%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/field_maps_configuration}/field_maps_configuration.ts (100%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/find_all_attack_discoveries}/find_all_attack_discoveries.ts (92%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/find_attack_discovery_by_connector_id}/find_attack_discovery_by_connector_id.test.ts (95%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/find_attack_discovery_by_connector_id}/find_attack_discovery_by_connector_id.ts (93%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/get_attack_discovery}/get_attack_discovery.test.ts (95%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/get_attack_discovery}/get_attack_discovery.ts (93%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence}/index.ts (92%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/transforms}/transforms.ts (98%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence}/types.ts (93%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/update_attack_discovery}/update_attack_discovery.test.ts (97%) rename x-pack/plugins/elastic_assistant/server/{ai_assistant_data_clients/attack_discovery => lib/attack_discovery/persistence/update_attack_discovery}/update_attack_discovery.ts (95%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => get}/get_attack_discovery.test.ts (85%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => get}/get_attack_discovery.ts (92%) delete mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => helpers}/helpers.ts (55%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post/cancel}/cancel_attack_discovery.test.ts (80%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post/cancel}/cancel_attack_discovery.ts (91%) create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post}/post_attack_discovery.test.ts (79%) rename x-pack/plugins/elastic_assistant/server/routes/attack_discovery/{ => post}/post_attack_discovery.ts (79%) create mode 100644 x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts create mode 100644 x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts delete mode 100644 x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts similarity index 96% rename from x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts rename to x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts index c8b52779d7b42..975896f381443 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.test.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query'; +import { getOpenAndAcknowledgedAlertsQuery } from '.'; describe('getOpenAndAcknowledgedAlertsQuery', () => { it('returns the expected query', () => { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts similarity index 87% rename from x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts rename to x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts index 4090e71baa371..6f6e196053ca6 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/get_open_and_acknowledged_alerts_query/index.ts @@ -5,8 +5,13 @@ * 2.0. */ -import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { AnonymizationFieldResponse } from '../../schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +/** + * This query returns open and acknowledged (non-building block) alerts in the last 24 hours. + * + * The alerts are ordered by risk score, and then from the most recent to the oldest. + */ export const getOpenAndAcknowledgedAlertsQuery = ({ alertsIndexPattern, anonymizationFields, diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts new file mode 100644 index 0000000000000..899b156d21767 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { getRawDataOrDefault } from '.'; + +describe('getRawDataOrDefault', () => { + it('returns the raw data when it is valid', () => { + const rawData = { + field1: [1, 2, 3], + field2: ['a', 'b', 'c'], + }; + + expect(getRawDataOrDefault(rawData)).toEqual(rawData); + }); + + it('returns an empty object when the raw data is invalid', () => { + const rawData = { + field1: [1, 2, 3], + field2: 'invalid', + }; + + expect(getRawDataOrDefault(rawData)).toEqual({}); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts new file mode 100644 index 0000000000000..edbe320c95305 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/get_raw_data_or_default/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRawDataValid } from '../is_raw_data_valid'; +import type { MaybeRawData } from '../types'; + +/** Returns the raw data if it valid, or a default if it's not */ +export const getRawDataOrDefault = (rawData: MaybeRawData): Record<string, unknown[]> => + isRawDataValid(rawData) ? rawData : {}; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts new file mode 100644 index 0000000000000..cc205250e84db --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { isRawDataValid } from '.'; + +describe('isRawDataValid', () => { + it('returns true for valid raw data', () => { + const rawData = { + field1: [1, 2, 3], // the Fields API may return a number array + field2: ['a', 'b', 'c'], // the Fields API may return a string array + }; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns true when a field array is empty', () => { + const rawData = { + field1: [1, 2, 3], // the Fields API may return a number array + field2: ['a', 'b', 'c'], // the Fields API may return a string array + field3: [], // the Fields API may return an empty array + }; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns false when a field does not have an array of values', () => { + const rawData = { + field1: [1, 2, 3], + field2: 'invalid', + }; + + expect(isRawDataValid(rawData)).toBe(false); + }); + + it('returns true for empty raw data', () => { + const rawData = {}; + + expect(isRawDataValid(rawData)).toBe(true); + }); + + it('returns false when raw data is an unexpected type', () => { + const rawData = 1234; + + // @ts-expect-error + expect(isRawDataValid(rawData)).toBe(false); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts new file mode 100644 index 0000000000000..1a9623b15ea98 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/is_raw_data_valid/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MaybeRawData } from '../types'; + +export const isRawDataValid = (rawData: MaybeRawData): rawData is Record<string, unknown[]> => + typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x])); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts new file mode 100644 index 0000000000000..b118a5c94b26e --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sizeIsOutOfRange } from '.'; +import { MAX_SIZE, MIN_SIZE } from '../types'; + +describe('sizeIsOutOfRange', () => { + it('returns true when size is undefined', () => { + const size = undefined; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns true when size is less than MIN_SIZE', () => { + const size = MIN_SIZE - 1; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns true when size is greater than MAX_SIZE', () => { + const size = MAX_SIZE + 1; + + expect(sizeIsOutOfRange(size)).toBe(true); + }); + + it('returns false when size is exactly MIN_SIZE', () => { + const size = MIN_SIZE; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + + it('returns false when size is exactly MAX_SIZE', () => { + const size = MAX_SIZE; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); + + it('returns false when size is within the valid range', () => { + const size = MIN_SIZE + 1; + + expect(sizeIsOutOfRange(size)).toBe(false); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts new file mode 100644 index 0000000000000..b2a93b79cbb42 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/size_is_out_of_range/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MAX_SIZE, MIN_SIZE } from '../types'; + +/** Return true if the provided size is out of range */ +export const sizeIsOutOfRange = (size?: number): boolean => + size == null || size < MIN_SIZE || size > MAX_SIZE; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts new file mode 100644 index 0000000000000..5c81c99ce5732 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/alerts/helpers/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +export const MIN_SIZE = 10; +export const MAX_SIZE = 10000; + +/** currently the same shape as "fields" property in the ES response */ +export type MaybeRawData = SearchResponse['fields'] | undefined; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts index 9599e8596e553..8ade6084fd7de 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.gen.ts @@ -39,7 +39,7 @@ export const AttackDiscovery = z.object({ /** * A short (no more than a sentence) summary of the attack discovery featuring only the host.name and user.name fields (when they are applicable), using the same syntax */ - entitySummaryMarkdown: z.string(), + entitySummaryMarkdown: z.string().optional(), /** * An array of MITRE ATT&CK tactic for the attack discovery */ @@ -55,7 +55,7 @@ export const AttackDiscovery = z.object({ /** * The time the attack discovery was generated */ - timestamp: NonEmptyString, + timestamp: NonEmptyString.optional(), }); /** diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml index dcb72147f9408..3adf2f7836804 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/attack_discovery/common_attributes.schema.yaml @@ -12,9 +12,7 @@ components: required: - 'alertIds' - 'detailsMarkdown' - - 'entitySummaryMarkdown' - 'summaryMarkdown' - - 'timestamp' - 'title' properties: alertIds: diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts index b6d51b9bea3fc..a0cbc22282c7b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.gen.ts @@ -22,10 +22,12 @@ export type PostEvaluateBody = z.infer<typeof PostEvaluateBody>; export const PostEvaluateBody = z.object({ graphs: z.array(z.string()), datasetName: z.string(), + evaluatorConnectorId: z.string().optional(), connectorIds: z.array(z.string()), runName: z.string().optional(), alertsIndexPattern: z.string().optional().default('.alerts-security.alerts-default'), langSmithApiKey: z.string().optional(), + langSmithProject: z.string().optional(), replacements: Replacements.optional().default({}), size: z.number().optional().default(20), }); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml index d0bec37344165..071d80156890b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/evaluation/post_evaluate_route.schema.yaml @@ -61,6 +61,8 @@ components: type: string datasetName: type: string + evaluatorConnectorId: + type: string connectorIds: type: array items: @@ -72,6 +74,8 @@ components: default: ".alerts-security.alerts-default" langSmithApiKey: type: string + langSmithProject: + type: string replacements: $ref: "../conversations/common_attributes.schema.yaml#/components/schemas/Replacements" default: {} diff --git a/x-pack/packages/kbn-elastic-assistant-common/index.ts b/x-pack/packages/kbn-elastic-assistant-common/index.ts index d8b4858d3ba8b..41ed86dacd9db 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/index.ts @@ -25,3 +25,19 @@ export { export { transformRawData } from './impl/data_anonymization/transform_raw_data'; export { parseBedrockBuffer, handleBedrockChunk } from './impl/utils/bedrock'; export * from './constants'; + +/** currently the same shape as "fields" property in the ES response */ +export { type MaybeRawData } from './impl/alerts/helpers/types'; + +/** + * This query returns open and acknowledged (non-building block) alerts in the last 24 hours. + * + * The alerts are ordered by risk score, and then from the most recent to the oldest. + */ +export { getOpenAndAcknowledgedAlertsQuery } from './impl/alerts/get_open_and_acknowledged_alerts_query'; + +/** Returns the raw data if it valid, or a default if it's not */ +export { getRawDataOrDefault } from './impl/alerts/helpers/get_raw_data_or_default'; + +/** Return true if the provided size is out of range */ +export { sizeIsOutOfRange } from './impl/alerts/helpers/size_is_out_of_range'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx index 60078178a1771..3b48c8d0861c5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx @@ -16,7 +16,7 @@ import * as i18n from '../../../knowledge_base/translations'; export const MIN_LATEST_ALERTS = 10; export const MAX_LATEST_ALERTS = 100; export const TICK_INTERVAL = 10; -export const RANGE_CONTAINER_WIDTH = 300; // px +export const RANGE_CONTAINER_WIDTH = 600; // px const LABEL_WRAPPER_MIN_WIDTH = 95; // px interface Props { @@ -52,6 +52,7 @@ const AlertsSettingsComponent = ({ knowledgeBase, setUpdatedKnowledgeBaseSetting <AlertsRange knowledgeBase={knowledgeBase} setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} + value={knowledgeBase.latestAlerts} /> <EuiSpacer size="s" /> </EuiFlexItem> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx index 1a6f826bd415f..7a3998879078d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_management.tsx @@ -40,6 +40,7 @@ export const AlertsSettingsManagement: React.FC<Props> = React.memo( knowledgeBase={knowledgeBase} setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} compressed={false} + value={knowledgeBase.latestAlerts} /> </EuiPanel> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx index cefc008eba992..ffbcad48d1cac 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx @@ -17,28 +17,34 @@ import { EuiComboBox, EuiButton, EuiComboBoxOptionOption, + EuiComboBoxSingleSelectionShape, EuiTextColor, EuiFieldText, + EuiFieldNumber, EuiFlexItem, EuiFlexGroup, EuiLink, EuiPanel, } from '@elastic/eui'; - import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { GetEvaluateResponse, PostEvaluateRequestBodyInput, } from '@kbn/elastic-assistant-common'; +import { isEmpty } from 'lodash/fp'; + import * as i18n from './translations'; import { useAssistantContext } from '../../../assistant_context'; +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '../../../assistant_context/constants'; import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; import { getActionTypeTitle, getGenAiConfig } from '../../../connectorland/helpers'; import { PRECONFIGURED_CONNECTOR } from '../../../connectorland/translations'; import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; import { useEvaluationData } from '../../api/evaluate/use_evaluation_data'; +const AS_PLAIN_TEXT: EuiComboBoxSingleSelectionShape = { asPlainText: true }; + /** * Evaluation Settings -- development-only feature for evaluating models */ @@ -121,6 +127,18 @@ export const EvaluationSettings: React.FC = React.memo(() => { }, [setSelectedModelOptions] ); + + const [selectedEvaluatorModel, setSelectedEvaluatorModel] = useState< + Array<EuiComboBoxOptionOption<string>> + >([]); + + const onSelectedEvaluatorModelChange = useCallback( + (selected: Array<EuiComboBoxOptionOption<string>>) => setSelectedEvaluatorModel(selected), + [] + ); + + const [size, setSize] = useState<string>(`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`); + const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); const modelOptions = useMemo(() => { return ( @@ -170,19 +188,40 @@ export const EvaluationSettings: React.FC = React.memo(() => { // Perform Evaluation Button const handlePerformEvaluation = useCallback(async () => { + const evaluatorConnectorId = + selectedEvaluatorModel[0]?.key != null + ? { evaluatorConnectorId: selectedEvaluatorModel[0].key } + : {}; + + const langSmithApiKey = isEmpty(traceOptions.langSmithApiKey) + ? undefined + : traceOptions.langSmithApiKey; + + const langSmithProject = isEmpty(traceOptions.langSmithProject) + ? undefined + : traceOptions.langSmithProject; + const evalParams: PostEvaluateRequestBodyInput = { connectorIds: selectedModelOptions.flatMap((option) => option.key ?? []).sort(), graphs: selectedGraphOptions.map((option) => option.label).sort(), datasetName: selectedDatasetOptions[0]?.label, + ...evaluatorConnectorId, + langSmithApiKey, + langSmithProject, runName, + size: Number(size), }; performEvaluation(evalParams); }, [ performEvaluation, runName, selectedDatasetOptions, + selectedEvaluatorModel, selectedGraphOptions, selectedModelOptions, + size, + traceOptions.langSmithApiKey, + traceOptions.langSmithProject, ]); const getSection = (title: string, description: string) => ( @@ -355,6 +394,29 @@ export const EvaluationSettings: React.FC = React.memo(() => { onChange={onGraphOptionsChange} /> </EuiFormRow> + + <EuiFormRow + display="rowCompressed" + helpText={i18n.EVALUATOR_MODEL_DESCRIPTION} + label={i18n.EVALUATOR_MODEL} + > + <EuiComboBox + aria-label={i18n.EVALUATOR_MODEL} + compressed + onChange={onSelectedEvaluatorModelChange} + options={modelOptions} + selectedOptions={selectedEvaluatorModel} + singleSelection={AS_PLAIN_TEXT} + /> + </EuiFormRow> + + <EuiFormRow + display="rowCompressed" + helpText={i18n.DEFAULT_MAX_ALERTS_DESCRIPTION} + label={i18n.DEFAULT_MAX_ALERTS} + > + <EuiFieldNumber onChange={(e) => setSize(e.target.value)} value={size} /> + </EuiFormRow> </EuiAccordion> <EuiHorizontalRule margin={'s'} /> <EuiFlexGroup alignItems="center"> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts index 62902d0f14095..26eddb8a223c7 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts @@ -78,6 +78,36 @@ export const CONNECTORS_LABEL = i18n.translate( } ); +export const EVALUATOR_MODEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelLabel', + { + defaultMessage: 'Evaluator model (optional)', + } +); + +export const DEFAULT_MAX_ALERTS = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.defaultMaxAlertsLabel', + { + defaultMessage: 'Default max alerts', + } +); + +export const EVALUATOR_MODEL_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelDescription', + { + defaultMessage: + 'Judge the quality of all predictions using a single model. (Default: use the same model as the connector)', + } +); + +export const DEFAULT_MAX_ALERTS_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.defaultMaxAlertsDescription', + { + defaultMessage: + 'The default maximum number of alerts to send as context, which may be overridden by the Example input', + } +); + export const CONNECTORS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx index be7724d882278..92a2a3df2683b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -10,7 +10,9 @@ import { KnowledgeBaseConfig } from '../assistant/types'; export const ATTACK_DISCOVERY_STORAGE_KEY = 'attackDiscovery'; export const DEFAULT_ASSISTANT_NAMESPACE = 'elasticAssistantDefault'; export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId'; +export const MAX_ALERTS_LOCAL_STORAGE_KEY = 'maxAlerts'; export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase'; +export const SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY = 'showSettingsTour'; export const STREAMING_LOCAL_STORAGE_KEY = 'streaming'; export const TRACE_OPTIONS_SESSION_STORAGE_KEY = 'traceOptions'; export const CONVERSATION_TABLE_SESSION_STORAGE_KEY = 'conversationTable'; @@ -21,6 +23,9 @@ export const ANONYMIZATION_TABLE_SESSION_STORAGE_KEY = 'anonymizationTable'; /** The default `n` latest alerts, ordered by risk score, sent as context to the assistant */ export const DEFAULT_LATEST_ALERTS = 20; +/** The default maximum number of alerts to be sent as context when generating Attack discoveries */ +export const DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS = 200; + export const DEFAULT_KNOWLEDGE_BASE_SETTINGS: KnowledgeBaseConfig = { latestAlerts: DEFAULT_LATEST_ALERTS, }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index c7b15f681a717..2319bf67de89a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -262,7 +262,10 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ docLinks, getComments, http, - knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, ...localStorageKnowledgeBase }, + knowledgeBase: { + ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, + ...localStorageKnowledgeBase, + }, promptContexts, navigateToApp, nameSpace, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx index 63bd86121dcc1..6cfa60eff282d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx @@ -7,7 +7,7 @@ import { EuiRange, useGeneratedHtmlId } from '@elastic/eui'; import { css } from '@emotion/react'; -import React from 'react'; +import React, { useCallback } from 'react'; import { MAX_LATEST_ALERTS, MIN_LATEST_ALERTS, @@ -16,35 +16,57 @@ import { import { KnowledgeBaseConfig } from '../assistant/types'; import { ALERTS_RANGE } from './translations'; +export type SingleRangeChangeEvent = + | React.ChangeEvent<HTMLInputElement> + | React.KeyboardEvent<HTMLInputElement> + | React.MouseEvent<HTMLButtonElement>; + interface Props { - knowledgeBase: KnowledgeBaseConfig; - setUpdatedKnowledgeBaseSettings: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>; compressed?: boolean; + maxAlerts?: number; + minAlerts?: number; + onChange?: (e: SingleRangeChangeEvent) => void; + knowledgeBase?: KnowledgeBaseConfig; + setUpdatedKnowledgeBaseSettings?: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>; + step?: number; + value: string | number; } const MAX_ALERTS_RANGE_WIDTH = 649; // px export const AlertsRange: React.FC<Props> = React.memo( - ({ knowledgeBase, setUpdatedKnowledgeBaseSettings, compressed = true }) => { + ({ + compressed = true, + knowledgeBase, + maxAlerts = MAX_LATEST_ALERTS, + minAlerts = MIN_LATEST_ALERTS, + onChange, + setUpdatedKnowledgeBaseSettings, + step = TICK_INTERVAL, + value, + }) => { const inputRangeSliderId = useGeneratedHtmlId({ prefix: 'inputRangeSlider' }); - return ( - <EuiRange - aria-label={ALERTS_RANGE} - compressed={compressed} - data-test-subj="alertsRange" - id={inputRangeSliderId} - max={MAX_LATEST_ALERTS} - min={MIN_LATEST_ALERTS} - onChange={(e) => + const handleOnChange = useCallback( + (e: SingleRangeChangeEvent) => { + if (knowledgeBase != null && setUpdatedKnowledgeBaseSettings != null) { setUpdatedKnowledgeBaseSettings({ ...knowledgeBase, latestAlerts: Number(e.currentTarget.value), - }) + }); } - showTicks - step={TICK_INTERVAL} - value={knowledgeBase.latestAlerts} + + if (onChange != null) { + onChange(e); + } + }, + [knowledgeBase, onChange, setUpdatedKnowledgeBaseSettings] + ); + + return ( + <EuiRange + aria-label={ALERTS_RANGE} + compressed={compressed} css={css` max-inline-size: ${MAX_ALERTS_RANGE_WIDTH}px; & .euiRangeTrack { @@ -52,6 +74,14 @@ export const AlertsRange: React.FC<Props> = React.memo( margin-inline-end: 0; } `} + data-test-subj="alertsRange" + id={inputRangeSliderId} + max={maxAlerts} + min={minAlerts} + onChange={handleOnChange} + showTicks + step={step} + value={value} /> ); } diff --git a/x-pack/packages/kbn-elastic-assistant/index.ts b/x-pack/packages/kbn-elastic-assistant/index.ts index 0baff57648cc8..7ec65c9601268 100644 --- a/x-pack/packages/kbn-elastic-assistant/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/index.ts @@ -77,10 +77,17 @@ export { AssistantAvatar } from './impl/assistant/assistant_avatar/assistant_ava export { ConnectorSelectorInline } from './impl/connectorland/connector_selector_inline/connector_selector_inline'; export { + /** The Attack discovery local storage key */ ATTACK_DISCOVERY_STORAGE_KEY, DEFAULT_ASSISTANT_NAMESPACE, + /** The default maximum number of alerts to be sent as context when generating Attack discoveries */ + DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, DEFAULT_LATEST_ALERTS, KNOWLEDGE_BASE_LOCAL_STORAGE_KEY, + /** The local storage key that specifies the maximum number of alerts to send as context */ + MAX_ALERTS_LOCAL_STORAGE_KEY, + /** The local storage key that specifies whether the settings tour should be shown */ + SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY, } from './impl/assistant_context/constants'; export { useLoadConnectors } from './impl/connectorland/use_load_connectors'; @@ -140,3 +147,16 @@ export { mergeBaseWithPersistedConversations } from './impl/assistant/helpers'; export { UpgradeButtons } from './impl/upgrade/upgrade_buttons'; export { getUserConversations, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; + +export { + /** A range slider component, typically used to configure the number of alerts sent as context */ + AlertsRange, + /** This event occurs when the `AlertsRange` slider is changed */ + type SingleRangeChangeEvent, +} from './impl/knowledge_base/alerts_range'; +export { + /** A label instructing the user to send fewer alerts */ + SELECT_FEWER_ALERTS, + /** Your anonymization settings will apply to these alerts (label) */ + YOUR_ANONYMIZATION_SETTINGS, +} from './impl/knowledge_base/translations'; diff --git a/x-pack/plugins/elastic_assistant/README.md b/x-pack/plugins/elastic_assistant/README.md index 2a1e47c177591..8cf2c0b8903dd 100755 --- a/x-pack/plugins/elastic_assistant/README.md +++ b/x-pack/plugins/elastic_assistant/README.md @@ -10,15 +10,21 @@ Maintained by the Security Solution team ## Graph structure +### Default Assistant graph + ![DefaultAssistantGraph](./docs/img/default_assistant_graph.png) +### Default Attack discovery graph + +![DefaultAttackDiscoveryGraph](./docs/img/default_attack_discovery_graph.png) + ## Development ### Generate graph structure To generate the graph structure, run `yarn draw-graph` from the plugin directory. -The graph will be generated in the `docs/img` directory of the plugin. +The graphs will be generated in the `docs/img` directory of the plugin. ### Testing -To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory. \ No newline at end of file +To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory. diff --git a/x-pack/plugins/elastic_assistant/docs/img/default_assistant_graph.png b/x-pack/plugins/elastic_assistant/docs/img/default_assistant_graph.png index e4ef8382317e5f827778d1bb34984644f87bbf9d..159b69c6d95723f08843347b2bb14d75ec2f3905 100644 GIT binary patch literal 29798 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°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 literal 30104 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 S?F<&7j~_pA#Dr2mr~U_C`zs#+ diff --git a/x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png b/x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..658490900cca60fc511e729fc08e8ea5e411d60f GIT binary patch literal 22551 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 X#QkvbKyG|{M>hY_zYE5pClmhyr^>}D literal 0 HcmV?d00001 diff --git a/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts b/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts index c44912ebf8d94..3b65d307ce385 100644 --- a/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts +++ b/x-pack/plugins/elastic_assistant/scripts/draw_graph_script.ts @@ -5,11 +5,13 @@ * 2.0. */ +import type { ElasticsearchClient } from '@kbn/core/server'; import { ToolingLog } from '@kbn/tooling-log'; import fs from 'fs/promises'; import path from 'path'; import { ActionsClientChatOpenAI, + type ActionsClientLlm, ActionsClientSimpleChatModel, } from '@kbn/langchain/server/language_models'; import type { Logger } from '@kbn/logging'; @@ -17,6 +19,11 @@ import { ChatPromptTemplate } from '@langchain/core/prompts'; import { FakeLLM } from '@langchain/core/utils/testing'; import { createOpenAIFunctionsAgent } from 'langchain/agents'; import { getDefaultAssistantGraph } from '../server/lib/langchain/graphs/default_assistant_graph/graph'; +import { getDefaultAttackDiscoveryGraph } from '../server/lib/attack_discovery/graphs/default_attack_discovery_graph'; + +interface Drawable { + drawMermaidPng: () => Promise<Blob>; +} // Just defining some test variables to get the graph to compile.. const testPrompt = ChatPromptTemplate.fromMessages([ @@ -34,7 +41,7 @@ const createLlmInstance = () => { return mockLlm; }; -async function getGraph(logger: Logger) { +async function getAssistantGraph(logger: Logger): Promise<Drawable> { const agentRunnable = await createOpenAIFunctionsAgent({ llm: mockLlm, tools: [], @@ -51,16 +58,49 @@ async function getGraph(logger: Logger) { return graph.getGraph(); } -export const draw = async () => { +async function getAttackDiscoveryGraph(logger: Logger): Promise<Drawable> { + const mockEsClient = {} as unknown as ElasticsearchClient; + + const graph = getDefaultAttackDiscoveryGraph({ + anonymizationFields: [], + esClient: mockEsClient, + llm: mockLlm as unknown as ActionsClientLlm, + logger, + replacements: {}, + size: 20, + }); + + return graph.getGraph(); +} + +export const drawGraph = async ({ + getGraph, + outputFilename, +}: { + getGraph: (logger: Logger) => Promise<Drawable>; + outputFilename: string; +}) => { const logger = new ToolingLog({ level: 'info', writeTo: process.stdout, }) as unknown as Logger; logger.info('Compiling graph'); - const outputPath = path.join(__dirname, '../docs/img/default_assistant_graph.png'); + const outputPath = path.join(__dirname, outputFilename); const graph = await getGraph(logger); const output = await graph.drawMermaidPng(); const buffer = Buffer.from(await output.arrayBuffer()); logger.info(`Writing graph to ${outputPath}`); await fs.writeFile(outputPath, buffer); }; + +export const draw = async () => { + await drawGraph({ + getGraph: getAssistantGraph, + outputFilename: '../docs/img/default_assistant_graph.png', + }); + + await drawGraph({ + getGraph: getAttackDiscoveryGraph, + outputFilename: '../docs/img/default_attack_discovery_graph.png', + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts index 9e8a0b5d2ac90..ee54e9c451ea2 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/attack_discovery_schema.mock.ts @@ -6,7 +6,7 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { EsAttackDiscoverySchema } from '../ai_assistant_data_clients/attack_discovery/types'; +import { EsAttackDiscoverySchema } from '../lib/attack_discovery/persistence/types'; export const getAttackDiscoverySearchEsMock = () => { const searchResponse: estypes.SearchResponse<EsAttackDiscoverySchema> = { diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts index 7e20e292a9868..473965a835f14 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/data_clients.mock.ts @@ -8,7 +8,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; import { AIAssistantDataClient } from '../ai_assistant_data_clients'; -import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; type ConversationsDataClientContract = PublicMethodsOf<AIAssistantConversationsDataClient>; export type ConversationsDataClientMock = jest.Mocked<ConversationsDataClientContract>; diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts index b52e7db536a3d..d53ceaa586975 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/request_context.ts @@ -26,7 +26,7 @@ import { GetAIAssistantKnowledgeBaseDataClientParams, } from '../ai_assistant_data_clients/knowledge_base'; import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; -import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts index def0a81acea37..ae736c77c30ef 100644 --- a/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts +++ b/x-pack/plugins/elastic_assistant/server/__mocks__/response.ts @@ -16,7 +16,7 @@ import { getPromptsSearchEsMock } from './prompts_schema.mock'; import { EsAnonymizationFieldsSchema } from '../ai_assistant_data_clients/anonymization_fields/types'; import { getAnonymizationFieldsSearchEsMock } from './anonymization_fields_schema.mock'; import { getAttackDiscoverySearchEsMock } from './attack_discovery_schema.mock'; -import { EsAttackDiscoverySchema } from '../ai_assistant_data_clients/attack_discovery/types'; +import { EsAttackDiscoverySchema } from '../lib/attack_discovery/persistence/types'; export const responseMock = { create: httpServerMock.createResponseFactory, diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts index 08912f41a8bbc..4cde64424ed7e 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts @@ -11,7 +11,7 @@ import type { AuthenticatedUser, Logger, ElasticsearchClient } from '@kbn/core/s import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import { Subject } from 'rxjs'; -import { attackDiscoveryFieldMap } from '../ai_assistant_data_clients/attack_discovery/field_maps_configuration'; +import { attackDiscoveryFieldMap } from '../lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration'; import { getDefaultAnonymizationFields } from '../../common/anonymization'; import { AssistantResourceNames, GetElser } from '../types'; import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; @@ -34,7 +34,7 @@ import { AIAssistantKnowledgeBaseDataClient, GetAIAssistantKnowledgeBaseDataClientParams, } from '../ai_assistant_data_clients/knowledge_base'; -import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence'; import { createGetElserId, createPipeline, pipelineExists } from './helpers'; const TOTAL_FIELDS_LIMIT = 2500; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.ts new file mode 100644 index 0000000000000..d149b8c4cd44d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_examples.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 type { Example } from 'langsmith/schemas'; + +export const exampleWithReplacements: Example = { + id: '5D436078-B2CF-487A-A0FA-7CB46696F54E', + created_at: '2024-10-10T23:01:19.350232+00:00', + dataset_id: '0DA3497B-B084-4105-AFC0-2D8E05DE4B7C', + modified_at: '2024-10-10T23:01:19.350232+00:00', + inputs: {}, + outputs: { + attackDiscoveries: [ + { + title: 'Critical Malware and Phishing Alerts on host e1cb3cf0-30f3-4f99-a9c8-518b955c6f90', + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + timestamp: '2024-10-10T22:59:52.749Z', + detailsMarkdown: + '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', + summaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} involving user {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', + mitreAttackTactics: ['Credential Access', 'Input Capture'], + entitySummaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }} involving user {{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}.', + }, + ], + replacements: { + '039c15c5-3964-43e7-a891-42fe2ceeb9ff': 'james', + '0b53f092-96dd-4282-bfb9-4f75a4530b80': 'root', + '1123bd7b-3afb-45d1-801a-108f04e7cfb7': 'SRVWIN04', + '3b9856bc-2c0d-4f1a-b9ae-32742e15ddd1': 'SRVWIN07', + '5306bcfd-2729-49e3-bdf0-678002778ccf': 'SRVWIN01', + '55af96a7-69b0-47cf-bf11-29be98a59eb0': 'SRVNIX05', + '66919fe3-16a4-4dfe-bc90-713f0b33a2ff': 'Administrator', + '9404361f-53fa-484f-adf8-24508256e70e': 'SRVWIN03', + 'e1cb3cf0-30f3-4f99-a9c8-518b955c6f90': 'SRVMAC08', + 'f59a00e2-f9c4-4069-8390-fd36ecd16918': 'SRVWIN02', + 'fc6d07da-5186-4d59-9b79-9382b0c226b3': 'SRVWIN06', + }, + }, + runs: [], +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts new file mode 100644 index 0000000000000..23c9c08ff5080 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/__mocks__/mock_runs.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Run } from 'langsmith/schemas'; + +export const runWithReplacements: Run = { + id: 'B7B03FEE-9AC4-4823-AEDB-F8EC20EAD5C4', + inputs: {}, + name: 'test', + outputs: { + attackDiscoveries: [ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` by the user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: + 'The host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` and user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}` were involved in the attack.', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name e1cb3cf0-30f3-4f99-a9c8-518b955c6f90 }}` involving the user `{{ user.name 039c15c5-3964-43e7-a891-42fe2ceeb9ff }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ], + replacements: { + '039c15c5-3964-43e7-a891-42fe2ceeb9ff': 'james', + '0b53f092-96dd-4282-bfb9-4f75a4530b80': 'root', + '1123bd7b-3afb-45d1-801a-108f04e7cfb7': 'SRVWIN04', + '3b9856bc-2c0d-4f1a-b9ae-32742e15ddd1': 'SRVWIN07', + '5306bcfd-2729-49e3-bdf0-678002778ccf': 'SRVWIN01', + '55af96a7-69b0-47cf-bf11-29be98a59eb0': 'SRVNIX05', + '66919fe3-16a4-4dfe-bc90-713f0b33a2ff': 'Administrator', + '9404361f-53fa-484f-adf8-24508256e70e': 'SRVWIN03', + 'e1cb3cf0-30f3-4f99-a9c8-518b955c6f90': 'SRVMAC08', + 'f59a00e2-f9c4-4069-8390-fd36ecd16918': 'SRVWIN02', + 'fc6d07da-5186-4d59-9b79-9382b0c226b3': 'SRVWIN06', + }, + }, + run_type: 'evaluation', +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts new file mode 100644 index 0000000000000..c6f6f09f1d9ae --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/constants.ts @@ -0,0 +1,911 @@ +/* + * 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 { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; + +export const DEFAULT_EVAL_ANONYMIZATION_FIELDS: AnonymizationFieldResponse[] = [ + { + id: 'Mx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: '_id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'NB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: '@timestamp', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'NR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'cloud.availability_zone', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Nh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'cloud.provider', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Nx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'cloud.region', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'OB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'destination.ip', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'OR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'dns.question.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Oh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'dns.question.type', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Ox09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.category', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'PB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.dataset', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'PR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.module', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Ph09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'event.outcome', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Px09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.Ext.original.path', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'QB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.hash.sha256', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'QR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Qh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'file.path', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Qx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'group.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'RB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'group.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'RR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.asset.criticality', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Rh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.name', + allowed: true, + anonymized: true, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Rx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.os.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'SB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.os.version', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'SR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.risk.calculated_level', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Sh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'host.risk.calculated_score_norm', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Sx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.original_time', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'TB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.risk_score', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'TR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.description', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Th09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Tx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.references', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'UB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.framework', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'UR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.tactic.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Uh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.tactic.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Ux09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.tactic.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'VB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'VR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Vh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Vx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.subtechnique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'WB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.subtechnique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'WR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.rule.threat.technique.subtechnique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Wh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.severity', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Wx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'kibana.alert.workflow_status', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'XB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'message', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'XR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'network.protocol', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Xh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.args', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Xx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.exists', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'YB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.signing_id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'YR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.status', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Yh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.subject_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Yx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.code_signature.trusted', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ZB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.command_line', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ZR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.executable', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Zh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.exit_code', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'Zx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.bytes_compressed_present', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'aB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.malware_signature.all_names', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'aR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.malware_signature.primary.matches', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ah09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.memory_region.malware_signature.primary.signature.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ax09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.Ext.token.integrity_level_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.hash.md5', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.hash.sha1', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.hash.sha256', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'bx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'cB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.args', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'cR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.args_count', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ch09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.exists', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'cx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.status', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.subject_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.code_signature.trusted', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.command_line', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'dx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.executable', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'eB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.parent.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'eR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.pe.original_file_name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'eh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.pid', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ex09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'process.working_directory', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.feature', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.data', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.entropy', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'fx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.extension', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.metrics', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.operation', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.path', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'gx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.files.score', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'Ransomware.version', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'rule.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'rule.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'hx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'source.ip', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'iB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.framework', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'iR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.tactic.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ih09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.tactic.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'ix09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.tactic.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'jx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.subtechnique.id', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.subtechnique.name', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'threat.technique.subtechnique.reference', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.asset.criticality', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'kx09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.domain', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'lB09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.name', + allowed: true, + anonymized: true, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'lR09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.risk.calculated_level', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, + { + id: 'lh09VpEBOiz7eA-eF2fb', + timestamp: '2024-08-15T13:32:10.073Z', + field: 'user.risk.calculated_score_norm', + allowed: true, + anonymized: false, + createdAt: '2024-08-15T13:32:10.073Z', + namespace: 'default', + }, +]; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts new file mode 100644 index 0000000000000..93d442bad5e9b --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { ExampleInput, ExampleInputWithOverrides } from '.'; + +const validInput = { + attackDiscoveries: null, + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [{ pageContent: 'content', metadata: { key: 'value' } }], + combinedGenerations: 'gen1gen2', + combinedRefinements: 'ref1ref2', + errors: ['error1', 'error2'], + generationAttempts: 1, + generations: ['gen1', 'gen2'], + hallucinationFailures: 0, + maxGenerationAttempts: 5, + maxHallucinationFailures: 2, + maxRepeatedGenerations: 3, + refinements: ['ref1', 'ref2'], + refinePrompt: 'refine prompt', + replacements: { key: 'replacement' }, + unrefinedResults: null, +}; + +describe('ExampleInput Schema', () => { + it('validates a correct ExampleInput object', () => { + expect(() => ExampleInput.parse(validInput)).not.toThrow(); + }); + + it('throws given an invalid ExampleInput', () => { + const invalidInput = { + attackDiscoveries: 'invalid', // should be an array or null + }; + + expect(() => ExampleInput.parse(invalidInput)).toThrow(); + }); + + it('removes unknown properties', () => { + const hasUnknownProperties = { + ...validInput, + unknownProperty: 'unknown', // <-- should be removed + }; + + const parsed = ExampleInput.parse(hasUnknownProperties); + + expect(parsed).not.toHaveProperty('unknownProperty'); + }); +}); + +describe('ExampleInputWithOverrides Schema', () => { + it('validates a correct ExampleInputWithOverrides object', () => { + const validInputWithOverrides = { + ...validInput, + overrides: { + attackDiscoveryPrompt: 'ad prompt override', + refinePrompt: 'refine prompt override', + }, + }; + + expect(() => ExampleInputWithOverrides.parse(validInputWithOverrides)).not.toThrow(); + }); + + it('throws when given an invalid ExampleInputWithOverrides object', () => { + const invalidInputWithOverrides = { + attackDiscoveries: null, + overrides: 'invalid', // should be an object + }; + + expect(() => ExampleInputWithOverrides.parse(invalidInputWithOverrides)).toThrow(); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts new file mode 100644 index 0000000000000..8183695fd7d2f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/example_input/index.ts @@ -0,0 +1,52 @@ +/* + * 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 { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import { z } from '@kbn/zod'; + +const Document = z.object({ + pageContent: z.string(), + metadata: z.record(z.string(), z.any()), +}); + +type Document = z.infer<typeof Document>; + +/** + * Parses the input from an example in a LangSmith dataset + */ +export const ExampleInput = z.object({ + attackDiscoveries: z.array(AttackDiscovery).nullable().optional(), + attackDiscoveryPrompt: z.string().optional(), + anonymizedAlerts: z.array(Document).optional(), + combinedGenerations: z.string().optional(), + combinedRefinements: z.string().optional(), + errors: z.array(z.string()).optional(), + generationAttempts: z.number().optional(), + generations: z.array(z.string()).optional(), + hallucinationFailures: z.number().optional(), + maxGenerationAttempts: z.number().optional(), + maxHallucinationFailures: z.number().optional(), + maxRepeatedGenerations: z.number().optional(), + refinements: z.array(z.string()).optional(), + refinePrompt: z.string().optional(), + replacements: Replacements.optional(), + unrefinedResults: z.array(AttackDiscovery).nullable().optional(), +}); + +export type ExampleInput = z.infer<typeof ExampleInput>; + +/** + * The optional overrides for an example input + */ +export const ExampleInputWithOverrides = z.intersection( + ExampleInput, + z.object({ + overrides: ExampleInput.optional(), + }) +); + +export type ExampleInputWithOverrides = z.infer<typeof ExampleInputWithOverrides>; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts new file mode 100644 index 0000000000000..8ea30103c0768 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { getDefaultPromptTemplate } from '.'; + +describe('getDefaultPromptTemplate', () => { + it('returns the expected prompt template', () => { + const expectedTemplate = `Evaluate based on how well the following submission follows the specified rubric. Grade only based on the rubric and "expected response": + +[BEGIN rubric] +1. Is the submission non-empty and not null? +2. Is the submission well-formed JSON? +3. Evaluate the value of the "detailsMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "detailsMarkdown" in the submission capture the essence of the "expected response", regardless of the order in which they appear, and highlight the same incident(s)? +4. Evaluate the value of the "entitySummaryMarkdown" field of all the "attackDiscoveries" in the submission json. Does the value of "entitySummaryMarkdown" in the submission mention at least 50% the same entities as in the "expected response"? +5. Evaluate the value of the "summaryMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "summaryMarkdown" in the submission at least partially similar to that of the "expected response", regardless of the order in which they appear, and summarize the same incident(s)? +6. Evaluate the value of the "title" field of all the "attackDiscoveries" in the submission json. Are the "title" values in the submission at least partially similar to the tile(s) of the "expected response", regardless of the order in which they appear, and mention the same incident(s)? +7. Evaluate the value of the "alertIds" field of all the "attackDiscoveries" in the submission json. Do they match at least 100% of the "alertIds" in the submission? +[END rubric] + +[BEGIN DATA] +{input} +[BEGIN submission] +{output} +[END submission] +[BEGIN expected response] +{reference} +[END expected response] +[END DATA] + +{criteria} Base your answer based on all the grading rubric items. If at least 5 of the 7 rubric items are correct, consider the submission correct. Write out your explanation for each criterion in the rubric, first in detail, then as a separate summary on a new line. + +Then finally respond with a single character, 'Y' or 'N', on a new line without any preceding or following characters. It's important that only a single character appears on the last line.`; + + const result = getDefaultPromptTemplate(); + + expect(result).toBe(expectedTemplate); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts new file mode 100644 index 0000000000000..08e10f00e7f77 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_default_prompt_template/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultPromptTemplate = + () => `Evaluate based on how well the following submission follows the specified rubric. Grade only based on the rubric and "expected response": + +[BEGIN rubric] +1. Is the submission non-empty and not null? +2. Is the submission well-formed JSON? +3. Evaluate the value of the "detailsMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "detailsMarkdown" in the submission capture the essence of the "expected response", regardless of the order in which they appear, and highlight the same incident(s)? +4. Evaluate the value of the "entitySummaryMarkdown" field of all the "attackDiscoveries" in the submission json. Does the value of "entitySummaryMarkdown" in the submission mention at least 50% the same entities as in the "expected response"? +5. Evaluate the value of the "summaryMarkdown" field of all the "attackDiscoveries" in the submission json. Do the values of "summaryMarkdown" in the submission at least partially similar to that of the "expected response", regardless of the order in which they appear, and summarize the same incident(s)? +6. Evaluate the value of the "title" field of all the "attackDiscoveries" in the submission json. Are the "title" values in the submission at least partially similar to the tile(s) of the "expected response", regardless of the order in which they appear, and mention the same incident(s)? +7. Evaluate the value of the "alertIds" field of all the "attackDiscoveries" in the submission json. Do they match at least 100% of the "alertIds" in the submission? +[END rubric] + +[BEGIN DATA] +{input} +[BEGIN submission] +{output} +[END submission] +[BEGIN expected response] +{reference} +[END expected response] +[END DATA] + +{criteria} Base your answer based on all the grading rubric items. If at least 5 of the 7 rubric items are correct, consider the submission correct. Write out your explanation for each criterion in the rubric, first in detail, then as a separate summary on a new line. + +Then finally respond with a single character, 'Y' or 'N', on a new line without any preceding or following characters. It's important that only a single character appears on the last line.`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts new file mode 100644 index 0000000000000..c261f151b99ab --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.test.ts @@ -0,0 +1,125 @@ +/* + * 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/fp'; + +import { getExampleAttackDiscoveriesWithReplacements } from '.'; +import { exampleWithReplacements } from '../../../__mocks__/mock_examples'; + +describe('getExampleAttackDiscoveriesWithReplacements', () => { + it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { + const result = getExampleAttackDiscoveriesWithReplacements(exampleWithReplacements); + + expect(result).toEqual([ + { + title: 'Critical Malware and Phishing Alerts on host SRVMAC08', + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + timestamp: '2024-10-10T22:59:52.749Z', + detailsMarkdown: + '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name SRVMAC08 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name james }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', + summaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', + mitreAttackTactics: ['Credential Access', 'Input Capture'], + entitySummaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}.', + }, + ]); + }); + + it('returns an empty entitySummaryMarkdown when the entitySummaryMarkdown is missing', () => { + const missingEntitySummaryMarkdown = omit( + 'entitySummaryMarkdown', + exampleWithReplacements.outputs?.attackDiscoveries?.[0] + ); + + const exampleWithMissingEntitySummaryMarkdown = { + ...exampleWithReplacements, + outputs: { + ...exampleWithReplacements.outputs, + attackDiscoveries: [missingEntitySummaryMarkdown], + }, + }; + + const result = getExampleAttackDiscoveriesWithReplacements( + exampleWithMissingEntitySummaryMarkdown + ); + + expect(result).toEqual([ + { + title: 'Critical Malware and Phishing Alerts on host SRVMAC08', + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + timestamp: '2024-10-10T22:59:52.749Z', + detailsMarkdown: + '- On `2023-06-19T00:28:38.061Z` a critical malware detection alert was triggered on host {{ host.name SRVMAC08 }} running {{ host.os.name macOS }} version {{ host.os.version 13.4 }}.\n- The malware was identified as {{ file.name unix1 }} with SHA256 hash {{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}.\n- The process {{ process.name My Go Application.app }} was executed with command line {{ process.command_line /private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app }}.\n- The process was not trusted as its code signature failed to satisfy specified code requirements.\n- The user involved was {{ user.name james }}.\n- Another critical alert was triggered for potential credentials phishing via {{ process.name osascript }} on the same host.\n- The phishing attempt involved displaying a dialog to capture the user\'s password.\n- The process {{ process.name osascript }} was executed with command line {{ process.command_line osascript -e display dialog "MacOS wants to access System Preferences\\n\\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬ }}.\n- The MITRE ATT&CK tactics involved include Credential Access and Input Capture.', + summaryMarkdown: + 'Critical malware and phishing alerts detected on {{ host.name SRVMAC08 }} involving user {{ user.name james }}. Malware identified as {{ file.name unix1 }} and phishing attempt via {{ process.name osascript }}.', + mitreAttackTactics: ['Credential Access', 'Input Capture'], + entitySummaryMarkdown: '', + }, + ]); + }); + + it('throws when an example is undefined', () => { + expect(() => getExampleAttackDiscoveriesWithReplacements(undefined)).toThrowError(); + }); + + it('throws when the example is missing attackDiscoveries', () => { + const missingAttackDiscoveries = { + ...exampleWithReplacements, + outputs: { + replacements: { ...exampleWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => + getExampleAttackDiscoveriesWithReplacements(missingAttackDiscoveries) + ).toThrowError(); + }); + + it('throws when attackDiscoveries is null', () => { + const nullAttackDiscoveries = { + ...exampleWithReplacements, + outputs: { + attackDiscoveries: null, + replacements: { ...exampleWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => getExampleAttackDiscoveriesWithReplacements(nullAttackDiscoveries)).toThrowError(); + }); + + it('returns the original attack discoveries when replacements are missing', () => { + const missingReplacements = { + ...exampleWithReplacements, + outputs: { + attackDiscoveries: [...exampleWithReplacements.outputs?.attackDiscoveries], + }, + }; + + const result = getExampleAttackDiscoveriesWithReplacements(missingReplacements); + + expect(result).toEqual(exampleWithReplacements.outputs?.attackDiscoveries); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.ts new file mode 100644 index 0000000000000..8fc5de2a08ed1 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_example_attack_discoveries_with_replacements/index.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 { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; +import type { Example } from 'langsmith/schemas'; + +import { getDiscoveriesWithOriginalValues } from '../../get_discoveries_with_original_values'; + +export const getExampleAttackDiscoveriesWithReplacements = ( + example: Example | undefined +): AttackDiscoveries => { + const exampleAttackDiscoveries = example?.outputs?.attackDiscoveries; + const exampleReplacements = example?.outputs?.replacements ?? {}; + + // NOTE: calls to `parse` throw an error if the Example input is invalid + const validatedAttackDiscoveries = AttackDiscoveries.parse(exampleAttackDiscoveries); + const validatedReplacements = Replacements.parse(exampleReplacements); + + const withReplacements = getDiscoveriesWithOriginalValues({ + attackDiscoveries: validatedAttackDiscoveries, + replacements: validatedReplacements, + }); + + return withReplacements; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts new file mode 100644 index 0000000000000..bd22e5d952b07 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.test.ts @@ -0,0 +1,117 @@ +/* + * 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/fp'; + +import { getRunAttackDiscoveriesWithReplacements } from '.'; +import { runWithReplacements } from '../../../__mocks__/mock_runs'; + +describe('getRunAttackDiscoveriesWithReplacements', () => { + it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { + const result = getRunAttackDiscoveriesWithReplacements(runWithReplacements); + + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: + 'The host `{{ host.name SRVMAC08 }}` and user `{{ user.name james }}` were involved in the attack.', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); + + it("returns an empty entitySummaryMarkdown when it's missing from the attack discovery", () => { + const missingEntitySummaryMarkdown = omit( + 'entitySummaryMarkdown', + runWithReplacements.outputs?.attackDiscoveries?.[0] + ); + + const runWithMissingEntitySummaryMarkdown = { + ...runWithReplacements, + outputs: { + ...runWithReplacements.outputs, + attackDiscoveries: [missingEntitySummaryMarkdown], + }, + }; + + const result = getRunAttackDiscoveriesWithReplacements(runWithMissingEntitySummaryMarkdown); + + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: '', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); + + it('throws when the run is missing attackDiscoveries', () => { + const missingAttackDiscoveries = { + ...runWithReplacements, + outputs: { + replacements: { ...runWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => getRunAttackDiscoveriesWithReplacements(missingAttackDiscoveries)).toThrowError(); + }); + + it('throws when attackDiscoveries is null', () => { + const nullAttackDiscoveries = { + ...runWithReplacements, + outputs: { + attackDiscoveries: null, + replacements: { ...runWithReplacements.outputs?.replacements }, + }, + }; + + expect(() => getRunAttackDiscoveriesWithReplacements(nullAttackDiscoveries)).toThrowError(); + }); + + it('returns the original attack discoveries when replacements are missing', () => { + const missingReplacements = { + ...runWithReplacements, + outputs: { + attackDiscoveries: [...runWithReplacements.outputs?.attackDiscoveries], + }, + }; + + const result = getRunAttackDiscoveriesWithReplacements(missingReplacements); + + expect(result).toEqual(runWithReplacements.outputs?.attackDiscoveries); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts new file mode 100644 index 0000000000000..01193320f712b --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/get_run_attack_discoveries_with_replacements/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { AttackDiscoveries, Replacements } from '@kbn/elastic-assistant-common'; +import type { Run } from 'langsmith/schemas'; + +import { getDiscoveriesWithOriginalValues } from '../../get_discoveries_with_original_values'; + +export const getRunAttackDiscoveriesWithReplacements = (run: Run): AttackDiscoveries => { + const runAttackDiscoveries = run.outputs?.attackDiscoveries; + const runReplacements = run.outputs?.replacements ?? {}; + + // NOTE: calls to `parse` throw an error if the Run Input is invalid + const validatedAttackDiscoveries = AttackDiscoveries.parse(runAttackDiscoveries); + const validatedReplacements = Replacements.parse(runReplacements); + + const withReplacements = getDiscoveriesWithOriginalValues({ + attackDiscoveries: validatedAttackDiscoveries, + replacements: validatedReplacements, + }); + + return withReplacements; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts new file mode 100644 index 0000000000000..829e27df73f14 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.test.ts @@ -0,0 +1,98 @@ +/* + * 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 { PromptTemplate } from '@langchain/core/prompts'; +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import { loadEvaluator } from 'langchain/evaluation'; + +import { type GetCustomEvaluatorOptions, getCustomEvaluator } from '.'; +import { getDefaultPromptTemplate } from './get_default_prompt_template'; +import { getExampleAttackDiscoveriesWithReplacements } from './get_example_attack_discoveries_with_replacements'; +import { getRunAttackDiscoveriesWithReplacements } from './get_run_attack_discoveries_with_replacements'; +import { exampleWithReplacements } from '../../__mocks__/mock_examples'; +import { runWithReplacements } from '../../__mocks__/mock_runs'; + +const mockLlm = jest.fn() as unknown as ActionsClientLlm; + +jest.mock('langchain/evaluation', () => ({ + ...jest.requireActual('langchain/evaluation'), + loadEvaluator: jest.fn().mockResolvedValue({ + evaluateStrings: jest.fn().mockResolvedValue({ + key: 'correctness', + score: 0.9, + }), + }), +})); + +const options: GetCustomEvaluatorOptions = { + criteria: 'correctness', + key: 'attack_discovery_correctness', + llm: mockLlm, + template: getDefaultPromptTemplate(), +}; + +describe('getCustomEvaluator', () => { + beforeEach(() => jest.clearAllMocks()); + + it('returns an evaluator function', () => { + const evaluator = getCustomEvaluator(options); + + expect(typeof evaluator).toBe('function'); + }); + + it('calls loadEvaluator with the expected arguments', async () => { + const evaluator = getCustomEvaluator(options); + + await evaluator(runWithReplacements, exampleWithReplacements); + + expect(loadEvaluator).toHaveBeenCalledWith('labeled_criteria', { + criteria: options.criteria, + chainOptions: { + prompt: PromptTemplate.fromTemplate(options.template), + }, + llm: mockLlm, + }); + }); + + it('calls evaluateStrings with the expected arguments', async () => { + const mockEvaluateStrings = jest.fn().mockResolvedValue({ + key: 'correctness', + score: 0.9, + }); + + (loadEvaluator as jest.Mock).mockResolvedValue({ + evaluateStrings: mockEvaluateStrings, + }); + + const evaluator = getCustomEvaluator(options); + + await evaluator(runWithReplacements, exampleWithReplacements); + + const prediction = getRunAttackDiscoveriesWithReplacements(runWithReplacements); + const reference = getExampleAttackDiscoveriesWithReplacements(exampleWithReplacements); + + expect(mockEvaluateStrings).toHaveBeenCalledWith({ + input: '', + prediction: JSON.stringify(prediction, null, 2), + reference: JSON.stringify(reference, null, 2), + }); + }); + + it('returns the expected result', async () => { + const evaluator = getCustomEvaluator(options); + + const result = await evaluator(runWithReplacements, exampleWithReplacements); + + expect(result).toEqual({ key: 'attack_discovery_correctness', score: 0.9 }); + }); + + it('throws given an undefined example', async () => { + const evaluator = getCustomEvaluator(options); + + await expect(async () => evaluator(runWithReplacements, undefined)).rejects.toThrow(); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts new file mode 100644 index 0000000000000..bcabe410c1b72 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_custom_evaluator/index.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import { PromptTemplate } from '@langchain/core/prompts'; +import type { EvaluationResult } from 'langsmith/evaluation'; +import type { Run, Example } from 'langsmith/schemas'; +import { CriteriaLike, loadEvaluator } from 'langchain/evaluation'; + +import { getExampleAttackDiscoveriesWithReplacements } from './get_example_attack_discoveries_with_replacements'; +import { getRunAttackDiscoveriesWithReplacements } from './get_run_attack_discoveries_with_replacements'; + +export interface GetCustomEvaluatorOptions { + /** + * Examples: + * - "conciseness" + * - "relevance" + * - "correctness" + * - "detail" + */ + criteria: CriteriaLike; + /** + * The evaluation score will use this key + */ + key: string; + /** + * LLm to use for evaluation + */ + llm: ActionsClientLlm; + /** + * A prompt template that uses the {input}, {submission}, and {reference} variables + */ + template: string; +} + +export type CustomEvaluator = ( + rootRun: Run, + example: Example | undefined +) => Promise<EvaluationResult>; + +export const getCustomEvaluator = + ({ criteria, key, llm, template }: GetCustomEvaluatorOptions): CustomEvaluator => + async (rootRun, example) => { + const chain = await loadEvaluator('labeled_criteria', { + criteria, + chainOptions: { + prompt: PromptTemplate.fromTemplate(template), + }, + llm, + }); + + const exampleAttackDiscoveriesWithReplacements = + getExampleAttackDiscoveriesWithReplacements(example); + + const runAttackDiscoveriesWithReplacements = getRunAttackDiscoveriesWithReplacements(rootRun); + + // NOTE: res contains a score, as well as the reasoning for the score + const res = await chain.evaluateStrings({ + input: '', // empty for now, but this could be the alerts, i.e. JSON.stringify(rootRun.outputs?.anonymizedAlerts, null, 2), + prediction: JSON.stringify(runAttackDiscoveriesWithReplacements, null, 2), + reference: JSON.stringify(exampleAttackDiscoveriesWithReplacements, null, 2), + }); + + return { key, score: res.score }; + }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts new file mode 100644 index 0000000000000..423248aa5c3d6 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { AttackDiscovery } from '@kbn/elastic-assistant-common'; +import { omit } from 'lodash/fp'; + +import { getDiscoveriesWithOriginalValues } from '.'; +import { runWithReplacements } from '../../__mocks__/mock_runs'; + +describe('getDiscoveriesWithOriginalValues', () => { + it('returns attack discoveries with replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title', () => { + const result = getDiscoveriesWithOriginalValues({ + attackDiscoveries: runWithReplacements.outputs?.attackDiscoveries, + replacements: runWithReplacements.outputs?.replacements, + }); + + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: + 'The host `{{ host.name SRVMAC08 }}` and user `{{ user.name james }}` were involved in the attack.', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); + + it("returns an empty entitySummaryMarkdown when it's missing from the attack discovery", () => { + const missingEntitySummaryMarkdown = omit( + 'entitySummaryMarkdown', + runWithReplacements.outputs?.attackDiscoveries?.[0] + ) as unknown as AttackDiscovery; + + const result = getDiscoveriesWithOriginalValues({ + attackDiscoveries: [missingEntitySummaryMarkdown], + replacements: runWithReplacements.outputs?.replacements, + }); + expect(result).toEqual([ + { + alertIds: [ + '4af5689eb58c2420efc0f7fad53c5bf9b8b6797e516d6ea87d6044ce25d54e16', + 'c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b', + '021b27d6bee0650a843be1d511119a3b5c7c8fdaeff922471ce0248ad27bd26c', + '6cc8d5f0e1c2b6c75219b001858f1be64194a97334be7a1e3572f8cfe6bae608', + 'f39a4013ed9609584a8a22dca902e896aa5b24d2da03e0eaab5556608fa682ac', + '909968e926e08a974c7df1613d98ebf1e2422afcb58e4e994beb47b063e85080', + '2c25a4dc31cd1ec254c2b19ea663fd0b09a16e239caa1218b4598801fb330da6', + '3bf907becb3a4f8e39a3b673e0d50fc954a7febef30c12891744c603760e4998', + ], + detailsMarkdown: + '- The attack began with the execution of a malicious file named `unix1` on the host `{{ host.name SRVMAC08 }}` by the user `{{ user.name james }}`.\n- The file `unix1` was detected at `{{ file.path /Users/james/unix1 }}` with a SHA256 hash of `{{ file.hash.sha256 0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231 }}`.\n- The process `{{ process.name My Go Application.app }}` was executed multiple times with different arguments, indicating potential persistence mechanisms.\n- The process `{{ process.name chmod }}` was used to change permissions of the file `unix1` to 777, making it executable.\n- A phishing attempt was detected via `osascript` on the same host, attempting to capture user credentials.\n- The attack involved multiple critical alerts, all indicating high-risk malware activity.', + entitySummaryMarkdown: '', + mitreAttackTactics: ['Initial Access', 'Execution', 'Persistence', 'Credential Access'], + summaryMarkdown: + 'A series of critical malware alerts were detected on the host `{{ host.name SRVMAC08 }}` involving the user `{{ user.name james }}`. The attack included the execution of a malicious file `unix1`, permission changes, and a phishing attempt via `osascript`.', + title: 'Critical Malware Attack on macOS Host', + timestamp: '2024-10-11T17:55:59.702Z', + }, + ]); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts new file mode 100644 index 0000000000000..1ef88e2208d1f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_discoveries_with_original_values/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + type AttackDiscovery, + Replacements, + replaceAnonymizedValuesWithOriginalValues, +} from '@kbn/elastic-assistant-common'; + +export const getDiscoveriesWithOriginalValues = ({ + attackDiscoveries, + replacements, +}: { + attackDiscoveries: AttackDiscovery[]; + replacements: Replacements; +}): AttackDiscovery[] => + attackDiscoveries.map((attackDiscovery) => ({ + ...attackDiscovery, + detailsMarkdown: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.detailsMarkdown, + replacements, + }), + entitySummaryMarkdown: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.entitySummaryMarkdown ?? '', + replacements, + }), + summaryMarkdown: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.summaryMarkdown, + replacements, + }), + title: replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.title, + replacements, + }), + })); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts new file mode 100644 index 0000000000000..132a819d44ec8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { loggerMock } from '@kbn/logging-mocks'; + +import { getEvaluatorLlm } from '.'; + +jest.mock('@kbn/langchain/server', () => ({ + ...jest.requireActual('@kbn/langchain/server'), + + ActionsClientLlm: jest.fn(), +})); + +const connectorTimeout = 1000; + +const evaluatorConnectorId = 'evaluator-connector-id'; +const evaluatorConnector = { + id: 'evaluatorConnectorId', + actionTypeId: '.gen-ai', + name: 'GPT-4o', + isPreconfigured: true, + isSystemAction: false, + isDeprecated: false, +} as Connector; + +const experimentConnector: Connector = { + name: 'Gemini 1.5 Pro 002', + actionTypeId: '.gemini', + config: { + apiUrl: 'https://example.com', + defaultModel: 'gemini-1.5-pro-002', + gcpRegion: 'test-region', + gcpProjectID: 'test-project-id', + }, + secrets: { + credentialsJson: '{}', + }, + id: 'gemini-1-5-pro-002', + isPreconfigured: true, + isSystemAction: false, + isDeprecated: false, +} as Connector; + +const logger = loggerMock.create(); + +describe('getEvaluatorLlm', () => { + beforeEach(() => jest.clearAllMocks()); + + describe('getting the evaluation connector', () => { + it("calls actionsClient.get with the evaluator connector ID when it's provided", async () => { + const actionsClient = { + get: jest.fn(), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(actionsClient.get).toHaveBeenCalledWith({ + id: evaluatorConnectorId, + throwIfSystemAction: false, + }); + }); + + it("calls actionsClient.get with the experiment connector ID when the evaluator connector ID isn't provided", async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(null), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId: undefined, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(actionsClient.get).toHaveBeenCalledWith({ + id: experimentConnector.id, + throwIfSystemAction: false, + }); + }); + + it('falls back to the experiment connector when the evaluator connector is not found', async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(null), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(ActionsClientLlm).toHaveBeenCalledWith( + expect.objectContaining({ + connectorId: experimentConnector.id, + }) + ); + }); + }); + + it('logs the expected connector names and types', async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(evaluatorConnector), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: undefined, + logger, + }); + + expect(logger.info).toHaveBeenCalledWith( + `The ${evaluatorConnector.name} (openai) connector will judge output from the ${experimentConnector.name} (gemini) connector` + ); + }); + + it('creates a new ActionsClientLlm instance with the expected traceOptions', async () => { + const actionsClient = { + get: jest.fn().mockResolvedValue(evaluatorConnector), + } as unknown as ActionsClient; + + await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey: 'test-api-key', + logger, + }); + + expect(ActionsClientLlm).toHaveBeenCalledWith( + expect.objectContaining({ + traceOptions: { + projectName: 'evaluators', + tracers: expect.any(Array), + }, + }) + ); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts new file mode 100644 index 0000000000000..236def9670d07 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_evaluator_llm/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { Logger } from '@kbn/core/server'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; + +import { getLlmType } from '../../../../../routes/utils'; + +export const getEvaluatorLlm = async ({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector, + langSmithApiKey, + logger, +}: { + actionsClient: PublicMethodsOf<ActionsClient>; + connectorTimeout: number; + evaluatorConnectorId: string | undefined; + experimentConnector: Connector; + langSmithApiKey: string | undefined; + logger: Logger; +}): Promise<ActionsClientLlm> => { + const evaluatorConnector = + (await actionsClient.get({ + id: evaluatorConnectorId ?? experimentConnector.id, // fallback to the experiment connector if the evaluator connector is not found: + throwIfSystemAction: false, + })) ?? experimentConnector; + + const evaluatorLlmType = getLlmType(evaluatorConnector.actionTypeId); + const experimentLlmType = getLlmType(experimentConnector.actionTypeId); + + logger.info( + `The ${evaluatorConnector.name} (${evaluatorLlmType}) connector will judge output from the ${experimentConnector.name} (${experimentLlmType}) connector` + ); + + const traceOptions = { + projectName: 'evaluators', + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: 'evaluators', + logger, + }), + ], + }; + + return new ActionsClientLlm({ + actionsClient, + connectorId: evaluatorConnector.id, + llmType: evaluatorLlmType, + logger, + temperature: 0, // zero temperature for evaluation + timeout: connectorTimeout, + traceOptions, + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts new file mode 100644 index 0000000000000..47f36bc6fb0e7 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.test.ts @@ -0,0 +1,121 @@ +/* + * 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/fp'; +import type { Example } from 'langsmith/schemas'; + +import { getGraphInputOverrides } from '.'; +import { exampleWithReplacements } from '../../__mocks__/mock_examples'; + +const exampleWithAlerts: Example = { + ...exampleWithReplacements, + outputs: { + ...exampleWithReplacements.outputs, + anonymizedAlerts: [ + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + ], + }, +}; + +const exampleWithNoReplacements: Example = { + ...exampleWithReplacements, + outputs: { + ...omit('replacements', exampleWithReplacements.outputs), + }, +}; + +describe('getGraphInputOverrides', () => { + describe('root-level outputs overrides', () => { + it('returns the anonymizedAlerts from the root level of the outputs when present', () => { + const overrides = getGraphInputOverrides(exampleWithAlerts.outputs); + + expect(overrides.anonymizedAlerts).toEqual(exampleWithAlerts.outputs?.anonymizedAlerts); + }); + + it('does NOT populate the anonymizedAlerts key when it does NOT exist in the outputs', () => { + const overrides = getGraphInputOverrides(exampleWithReplacements.outputs); + + expect(overrides).not.toHaveProperty('anonymizedAlerts'); + }); + + it('returns replacements from the root level of the outputs when present', () => { + const overrides = getGraphInputOverrides(exampleWithReplacements.outputs); + + expect(overrides.replacements).toEqual(exampleWithReplacements.outputs?.replacements); + }); + + it('does NOT populate the replacements key when it does NOT exist in the outputs', () => { + const overrides = getGraphInputOverrides(exampleWithNoReplacements.outputs); + + expect(overrides).not.toHaveProperty('replacements'); + }); + + it('removes unknown properties', () => { + const withUnknownProperties = { + ...exampleWithReplacements, + outputs: { + ...exampleWithReplacements.outputs, + unknownProperty: 'unknown', + }, + }; + + const overrides = getGraphInputOverrides(withUnknownProperties.outputs); + + expect(overrides).not.toHaveProperty('unknownProperty'); + }); + }); + + describe('overrides', () => { + it('returns all overrides at the root level', () => { + const exampleWithOverrides = { + ...exampleWithAlerts, + outputs: { + ...exampleWithAlerts.outputs, + overrides: { + attackDiscoveries: [], + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [], + combinedGenerations: 'combinedGenerations', + combinedRefinements: 'combinedRefinements', + errors: ['error'], + generationAttempts: 1, + generations: ['generation'], + hallucinationFailures: 2, + maxGenerationAttempts: 3, + maxHallucinationFailures: 4, + maxRepeatedGenerations: 5, + refinements: ['refinement'], + refinePrompt: 'refinePrompt', + replacements: {}, + unrefinedResults: [], + }, + }, + }; + + const overrides = getGraphInputOverrides(exampleWithOverrides.outputs); + + expect(overrides).toEqual({ + ...exampleWithOverrides.outputs?.overrides, + }); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.ts new file mode 100644 index 0000000000000..232218f4386f8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/helpers/get_graph_input_overrides/index.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 { pick } from 'lodash/fp'; + +import { ExampleInputWithOverrides } from '../../example_input'; +import { GraphState } from '../../../graphs/default_attack_discovery_graph/types'; + +/** + * Parses input from an LangSmith dataset example to get the graph input overrides + */ +export const getGraphInputOverrides = (outputs: unknown): Partial<GraphState> => { + const validatedInput = ExampleInputWithOverrides.safeParse(outputs).data ?? {}; // safeParse removes unknown properties + + const { overrides } = validatedInput; + + // return all overrides at the root level: + return { + // pick extracts just the anonymizedAlerts and replacements from the root level of the input, + // and only adds the anonymizedAlerts key if it exists in the input + ...pick('anonymizedAlerts', validatedInput), + ...pick('replacements', validatedInput), + ...overrides, // bring all other overrides to the root level + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts new file mode 100644 index 0000000000000..40b0f080fe54a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/index.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger } from '@kbn/core/server'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import { asyncForEach } from '@kbn/std'; +import { PublicMethodsOf } from '@kbn/utility-types'; + +import { DEFAULT_EVAL_ANONYMIZATION_FIELDS } from './constants'; +import { AttackDiscoveryGraphMetadata } from '../../langchain/graphs'; +import { DefaultAttackDiscoveryGraph } from '../graphs/default_attack_discovery_graph'; +import { getLlmType } from '../../../routes/utils'; +import { runEvaluations } from './run_evaluations'; + +export const evaluateAttackDiscovery = async ({ + actionsClient, + attackDiscoveryGraphs, + alertsIndexPattern, + anonymizationFields = DEFAULT_EVAL_ANONYMIZATION_FIELDS, // determines which fields are included in the alerts + connectors, + connectorTimeout, + datasetName, + esClient, + evaluationId, + evaluatorConnectorId, + langSmithApiKey, + langSmithProject, + logger, + runName, + size, +}: { + actionsClient: PublicMethodsOf<ActionsClient>; + attackDiscoveryGraphs: AttackDiscoveryGraphMetadata[]; + alertsIndexPattern: string; + anonymizationFields?: AnonymizationFieldResponse[]; + connectors: Connector[]; + connectorTimeout: number; + datasetName: string; + esClient: ElasticsearchClient; + evaluationId: string; + evaluatorConnectorId: string | undefined; + langSmithApiKey: string | undefined; + langSmithProject: string | undefined; + logger: Logger; + runName: string; + size: number; +}): Promise<void> => { + await asyncForEach(attackDiscoveryGraphs, async ({ getDefaultAttackDiscoveryGraph }) => { + // create a graph for every connector: + const graphs: Array<{ + connector: Connector; + graph: DefaultAttackDiscoveryGraph; + llmType: string | undefined; + name: string; + traceOptions: { + projectName: string | undefined; + tracers: LangChainTracer[]; + }; + }> = connectors.map((connector) => { + const llmType = getLlmType(connector.actionTypeId); + + const traceOptions = { + projectName: langSmithProject, + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: langSmithProject, + logger, + }), + ], + }; + + const llm = new ActionsClientLlm({ + actionsClient, + connectorId: connector.id, + llmType, + logger, + temperature: 0, // zero temperature for attack discovery, because we want structured JSON output + timeout: connectorTimeout, + traceOptions, + }); + + const graph = getDefaultAttackDiscoveryGraph({ + alertsIndexPattern, + anonymizationFields, + esClient, + llm, + logger, + size, + }); + + return { + connector, + graph, + llmType, + name: `${runName} - ${connector.name} - ${evaluationId} - Attack discovery`, + traceOptions, + }; + }); + + // run the evaluations for each graph: + await runEvaluations({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + datasetName, + graphs, + langSmithApiKey, + logger, + }); + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts new file mode 100644 index 0000000000000..19eb99d57c84c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/evaluation/run_evaluations/index.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { Logger } from '@kbn/core/server'; +import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; +import { asyncForEach } from '@kbn/std'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { Client } from 'langsmith'; +import { evaluate } from 'langsmith/evaluation'; + +import { getEvaluatorLlm } from '../helpers/get_evaluator_llm'; +import { getCustomEvaluator } from '../helpers/get_custom_evaluator'; +import { getDefaultPromptTemplate } from '../helpers/get_custom_evaluator/get_default_prompt_template'; +import { getGraphInputOverrides } from '../helpers/get_graph_input_overrides'; +import { DefaultAttackDiscoveryGraph } from '../../graphs/default_attack_discovery_graph'; +import { GraphState } from '../../graphs/default_attack_discovery_graph/types'; + +/** + * Runs an evaluation for each graph so they show up separately (resulting in + * each dataset run grouped by connector) + */ +export const runEvaluations = async ({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + datasetName, + graphs, + langSmithApiKey, + logger, +}: { + actionsClient: PublicMethodsOf<ActionsClient>; + connectorTimeout: number; + evaluatorConnectorId: string | undefined; + datasetName: string; + graphs: Array<{ + connector: Connector; + graph: DefaultAttackDiscoveryGraph; + llmType: string | undefined; + name: string; + traceOptions: { + projectName: string | undefined; + tracers: LangChainTracer[]; + }; + }>; + langSmithApiKey: string | undefined; + logger: Logger; +}): Promise<void> => + asyncForEach(graphs, async ({ connector, graph, llmType, name, traceOptions }) => { + const subject = `connector "${connector.name}" (${llmType}), running experiment "${name}"`; + + try { + logger.info( + () => + `Evaluating ${subject} with dataset "${datasetName}" and evaluator "${evaluatorConnectorId}"` + ); + + const predict = async (input: unknown): Promise<GraphState> => { + logger.debug(() => `Raw example Input for ${subject}":\n ${input}`); + + // The example `Input` may have overrides for the initial state of the graph: + const overrides = getGraphInputOverrides(input); + + return graph.invoke( + { + ...overrides, + }, + { + callbacks: [...(traceOptions.tracers ?? [])], + runName: name, + tags: ['evaluation', llmType ?? ''], + } + ); + }; + + const llm = await getEvaluatorLlm({ + actionsClient, + connectorTimeout, + evaluatorConnectorId, + experimentConnector: connector, + langSmithApiKey, + logger, + }); + + const customEvaluator = getCustomEvaluator({ + criteria: 'correctness', + key: 'attack_discovery_correctness', + llm, + template: getDefaultPromptTemplate(), + }); + + const evalOutput = await evaluate(predict, { + client: new Client({ apiKey: langSmithApiKey }), + data: datasetName ?? '', + evaluators: [customEvaluator], + experimentPrefix: name, + maxConcurrency: 5, // prevents rate limiting + }); + + logger.info(() => `Evaluation complete for ${subject}`); + + logger.debug( + () => `Evaluation output for ${subject}:\n ${JSON.stringify(evalOutput, null, 2)}` + ); + } catch (e) { + logger.error(`Error evaluating ${subject}: ${e}`); + } + }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts new file mode 100644 index 0000000000000..fb5df8f26d0c2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/constants.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// LangGraph metadata +export const ATTACK_DISCOVERY_GRAPH_RUN_NAME = 'Attack discovery'; +export const ATTACK_DISCOVERY_TAG = 'attack-discovery'; + +// Limits +export const DEFAULT_MAX_GENERATION_ATTEMPTS = 10; +export const DEFAULT_MAX_HALLUCINATION_FAILURES = 5; +export const DEFAULT_MAX_REPEATED_GENERATIONS = 3; + +export const NodeType = { + GENERATE_NODE: 'generate', + REFINE_NODE: 'refine', + RETRIEVE_ANONYMIZED_ALERTS_NODE: 'retrieve_anonymized_alerts', +} as const; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.ts new file mode 100644 index 0000000000000..225c4a2b8935c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.test.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 { getGenerateOrEndDecision } from '.'; + +describe('getGenerateOrEndDecision', () => { + it('returns "end" when hasZeroAlerts is true', () => { + const result = getGenerateOrEndDecision(true); + + expect(result).toEqual('end'); + }); + + it('returns "generate" when hasZeroAlerts is false', () => { + const result = getGenerateOrEndDecision(false); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts new file mode 100644 index 0000000000000..b134b2f3a6118 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/helpers/get_generate_or_end_decision/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getGenerateOrEndDecision = (hasZeroAlerts: boolean): 'end' | 'generate' => + hasZeroAlerts ? 'end' : 'generate'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts new file mode 100644 index 0000000000000..06dd1529179fa --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { loggerMock } from '@kbn/logging-mocks'; + +import { getGenerateOrEndEdge } from '.'; +import type { GraphState } from '../../types'; + +const logger = loggerMock.create(); + +const graphState: GraphState = { + attackDiscoveries: null, + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [ + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + ], + combinedGenerations: 'generations', + combinedRefinements: 'refinements', + errors: [], + generationAttempts: 0, + generations: [], + hallucinationFailures: 0, + maxGenerationAttempts: 10, + maxHallucinationFailures: 5, + maxRepeatedGenerations: 10, + refinements: [], + refinePrompt: 'refinePrompt', + replacements: {}, + unrefinedResults: null, +}; + +describe('getGenerateOrEndEdge', () => { + beforeEach(() => jest.clearAllMocks()); + + it("returns 'end' when there are zero alerts", () => { + const state: GraphState = { + ...graphState, + anonymizedAlerts: [], // <-- zero alerts + }; + + const edge = getGenerateOrEndEdge(logger); + const result = edge(state); + + expect(result).toEqual('end'); + }); + + it("returns 'generate' when there are alerts", () => { + const edge = getGenerateOrEndEdge(logger); + const result = edge(graphState); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.ts new file mode 100644 index 0000000000000..5bfc4912298eb --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_end/index.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 type { Logger } from '@kbn/core/server'; + +import { getGenerateOrEndDecision } from './helpers/get_generate_or_end_decision'; +import { getHasZeroAlerts } from '../helpers/get_has_zero_alerts'; +import type { GraphState } from '../../types'; + +export const getGenerateOrEndEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'end' | 'generate' => { + logger?.debug(() => '---GENERATE OR END---'); + const { anonymizedAlerts } = state; + + const hasZeroAlerts = getHasZeroAlerts(anonymizedAlerts); + + const decision = getGenerateOrEndDecision(hasZeroAlerts); + + logger?.debug( + () => `generatOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( + { + anonymizedAlerts: anonymizedAlerts.length, + hasZeroAlerts, + }, + null, + 2 + )} +\n---GENERATE OR END: ${decision}---` + ); + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts new file mode 100644 index 0000000000000..42c63b18459ed --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getGenerateOrRefineOrEndDecision } from '.'; + +describe('getGenerateOrRefineOrEndDecision', () => { + it("returns 'end' if getShouldEnd returns true", () => { + const result = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults: false, + hasZeroAlerts: true, + maxHallucinationFailuresReached: true, + maxRetriesReached: true, + }); + + expect(result).toEqual('end'); + }); + + it("returns 'refine' if hasUnrefinedResults is true and getShouldEnd returns false", () => { + const result = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults: true, + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toEqual('refine'); + }); + + it("returns 'generate' if hasUnrefinedResults is false and getShouldEnd returns false", () => { + const result = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults: false, + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts new file mode 100644 index 0000000000000..b409f63f71a69 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_generate_or_refine_or_end_decision/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { getShouldEnd } from '../get_should_end'; + +export const getGenerateOrRefineOrEndDecision = ({ + hasUnrefinedResults, + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasUnrefinedResults: boolean; + hasZeroAlerts: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): 'end' | 'generate' | 'refine' => { + if (getShouldEnd({ hasZeroAlerts, maxHallucinationFailuresReached, maxRetriesReached })) { + return 'end'; + } else if (hasUnrefinedResults) { + return 'refine'; + } else { + return 'generate'; + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts new file mode 100644 index 0000000000000..82480a6ad6889 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { getShouldEnd } from '.'; + +describe('getShouldEnd', () => { + it('returns true if hasZeroAlerts is true', () => { + const result = getShouldEnd({ + hasZeroAlerts: true, // <-- true + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toBe(true); + }); + + it('returns true if maxHallucinationFailuresReached is true', () => { + const result = getShouldEnd({ + hasZeroAlerts: false, + maxHallucinationFailuresReached: true, // <-- true + maxRetriesReached: false, + }); + + expect(result).toBe(true); + }); + + it('returns true if maxRetriesReached is true', () => { + const result = getShouldEnd({ + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: true, // <-- true + }); + + expect(result).toBe(true); + }); + + it('returns false if all conditions are false', () => { + const result = getShouldEnd({ + hasZeroAlerts: false, + maxHallucinationFailuresReached: false, + maxRetriesReached: false, + }); + + expect(result).toBe(false); + }); + + it('returns true if all conditions are true', () => { + const result = getShouldEnd({ + hasZeroAlerts: true, + maxHallucinationFailuresReached: true, + maxRetriesReached: true, + }); + + expect(result).toBe(true); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts new file mode 100644 index 0000000000000..9724ba25886fa --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/helpers/get_should_end/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getShouldEnd = ({ + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasZeroAlerts: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): boolean => hasZeroAlerts || maxRetriesReached || maxHallucinationFailuresReached; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts new file mode 100644 index 0000000000000..585a1bc2dcac3 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggerMock } from '@kbn/logging-mocks'; + +import { getGenerateOrRefineOrEndEdge } from '.'; +import type { GraphState } from '../../types'; + +const logger = loggerMock.create(); + +const graphState: GraphState = { + attackDiscoveries: null, + attackDiscoveryPrompt: 'prompt', + anonymizedAlerts: [ + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,e809ffc5e0c2e731c1f146e0f74250078136a87574534bf8e9ee55445894f7fc\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + { + metadata: {}, + pageContent: + '@timestamp,2024-10-10T21:01:24.148Z\n' + + '_id,c675d7eb6ee181d788b474117bae8d3ed4bdc2168605c330a93dd342534fb02b\n' + + 'host.name,e1cb3cf0-30f3-4f99-a9c8-518b955c6f90\n' + + 'user.name,039c15c5-3964-43e7-a891-42fe2ceeb9ff', + }, + ], + combinedGenerations: '', + combinedRefinements: '', + errors: [], + generationAttempts: 0, + generations: [], + hallucinationFailures: 0, + maxGenerationAttempts: 10, + maxHallucinationFailures: 5, + maxRepeatedGenerations: 3, + refinements: [], + refinePrompt: 'refinePrompt', + replacements: {}, + unrefinedResults: null, +}; + +describe('getGenerateOrRefineOrEndEdge', () => { + beforeEach(() => jest.clearAllMocks()); + + it('returns "end" when there are zero alerts', () => { + const withZeroAlerts: GraphState = { + ...graphState, + anonymizedAlerts: [], // <-- zero alerts + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withZeroAlerts); + + expect(result).toEqual('end'); + }); + + it('returns "end" when max hallucination failures are reached', () => { + const withMaxHallucinationFailures: GraphState = { + ...graphState, + hallucinationFailures: 5, + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withMaxHallucinationFailures); + + expect(result).toEqual('end'); + }); + + it('returns "end" when max retries are reached', () => { + const withMaxRetries: GraphState = { + ...graphState, + generationAttempts: 10, + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withMaxRetries); + + expect(result).toEqual('end'); + }); + + it('returns refine when there are unrefined results', () => { + const withUnrefinedResults: GraphState = { + ...graphState, + unrefinedResults: [ + { + alertIds: [], + id: 'test-id', + detailsMarkdown: 'test-details', + entitySummaryMarkdown: 'test-summary', + summaryMarkdown: 'test-summary', + title: 'test-title', + timestamp: '2024-10-10T21:01:24.148Z', + }, + ], + }; + + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(withUnrefinedResults); + + expect(result).toEqual('refine'); + }); + + it('return generate when there are no unrefined results', () => { + const edge = getGenerateOrRefineOrEndEdge(logger); + const result = edge(graphState); + + expect(result).toEqual('generate'); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts new file mode 100644 index 0000000000000..3368a04ec9204 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/generate_or_refine_or_end/index.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import { getGenerateOrRefineOrEndDecision } from './helpers/get_generate_or_refine_or_end_decision'; +import { getHasResults } from '../helpers/get_has_results'; +import { getHasZeroAlerts } from '../helpers/get_has_zero_alerts'; +import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; +import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; +import type { GraphState } from '../../types'; + +export const getGenerateOrRefineOrEndEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'end' | 'generate' | 'refine' => { + logger?.debug(() => '---GENERATE OR REFINE OR END---'); + const { + anonymizedAlerts, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + unrefinedResults, + } = state; + + const hasZeroAlerts = getHasZeroAlerts(anonymizedAlerts); + const hasUnrefinedResults = getHasResults(unrefinedResults); + const maxRetriesReached = getMaxRetriesReached({ generationAttempts, maxGenerationAttempts }); + const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ + hallucinationFailures, + maxHallucinationFailures, + }); + + const decision = getGenerateOrRefineOrEndDecision({ + hasUnrefinedResults, + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, + }); + + logger?.debug( + () => + `generatOrRefineOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( + { + anonymizedAlerts: anonymizedAlerts.length, + generationAttempts, + hallucinationFailures, + hasUnrefinedResults, + hasZeroAlerts, + maxHallucinationFailuresReached, + maxRetriesReached, + unrefinedResults: unrefinedResults?.length ?? 0, + }, + null, + 2 + )} + \n---GENERATE OR REFINE OR END: ${decision}---` + ); + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts new file mode 100644 index 0000000000000..413f01b74dece --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_results/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AttackDiscovery } from '@kbn/elastic-assistant-common'; + +export const getHasResults = (attackDiscoveries: AttackDiscovery[] | null): boolean => + attackDiscoveries !== null; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts new file mode 100644 index 0000000000000..d768b363f101e --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/helpers/get_has_zero_alerts/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Document } from '@langchain/core/documents'; +import { isEmpty } from 'lodash/fp'; + +export const getHasZeroAlerts = (anonymizedAlerts: Document[]): boolean => + isEmpty(anonymizedAlerts); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts new file mode 100644 index 0000000000000..7168aa08aeef2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_refine_or_end_decision/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getShouldEnd } from '../get_should_end'; + +export const getRefineOrEndDecision = ({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasFinalResults: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): 'refine' | 'end' => + getShouldEnd({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, + }) + ? 'end' + : 'refine'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts new file mode 100644 index 0000000000000..697f93dd3a02f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/helpers/get_should_end/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getShouldEnd = ({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + hasFinalResults: boolean; + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): boolean => hasFinalResults || maxRetriesReached || maxHallucinationFailuresReached; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts new file mode 100644 index 0000000000000..85140dceafdcb --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/refine_or_end/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import { getRefineOrEndDecision } from './helpers/get_refine_or_end_decision'; +import { getHasResults } from '../helpers/get_has_results'; +import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; +import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; +import type { GraphState } from '../../types'; + +export const getRefineOrEndEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'end' | 'refine' => { + logger?.debug(() => '---REFINE OR END---'); + const { + attackDiscoveries, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + } = state; + + const hasFinalResults = getHasResults(attackDiscoveries); + const maxRetriesReached = getMaxRetriesReached({ generationAttempts, maxGenerationAttempts }); + const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ + hallucinationFailures, + maxHallucinationFailures, + }); + + const decision = getRefineOrEndDecision({ + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, + }); + + logger?.debug( + () => + `refineOrEndEdge evaluated the following (derived) state:\n${JSON.stringify( + { + attackDiscoveries: attackDiscoveries?.length ?? 0, + generationAttempts, + hallucinationFailures, + hasFinalResults, + maxHallucinationFailuresReached, + maxRetriesReached, + }, + null, + 2 + )} + \n---REFINE OR END: ${decision}---` + ); + + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts new file mode 100644 index 0000000000000..050ca17484185 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/get_retrieve_or_generate/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Document } from '@langchain/core/documents'; + +export const getRetrieveOrGenerate = ( + anonymizedAlerts: Document[] +): 'retrieve_anonymized_alerts' | 'generate' => + anonymizedAlerts.length === 0 ? 'retrieve_anonymized_alerts' : 'generate'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts new file mode 100644 index 0000000000000..ad0512497d07d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/edges/retrieve_anonymized_alerts_or_generate/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import { getRetrieveOrGenerate } from './get_retrieve_or_generate'; +import type { GraphState } from '../../types'; + +export const getRetrieveAnonymizedAlertsOrGenerateEdge = (logger?: Logger) => { + const edge = (state: GraphState): 'retrieve_anonymized_alerts' | 'generate' => { + logger?.debug(() => '---RETRIEVE ANONYMIZED ALERTS OR GENERATE---'); + const { anonymizedAlerts } = state; + + const decision = getRetrieveOrGenerate(anonymizedAlerts); + + logger?.debug( + () => + `retrieveAnonymizedAlertsOrGenerateEdge evaluated the following (derived) state:\n${JSON.stringify( + { + anonymizedAlerts: anonymizedAlerts.length, + }, + null, + 2 + )} + \n---RETRIEVE ANONYMIZED ALERTS OR GENERATE: ${decision}---` + ); + + return decision; + }; + + return edge; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts new file mode 100644 index 0000000000000..07985381afa73 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_hallucination_failures_reached/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getMaxHallucinationFailuresReached = ({ + hallucinationFailures, + maxHallucinationFailures, +}: { + hallucinationFailures: number; + maxHallucinationFailures: number; +}): boolean => hallucinationFailures >= maxHallucinationFailures; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts new file mode 100644 index 0000000000000..c1e36917b45cf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/helpers/get_max_retries_reached/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getMaxRetriesReached = ({ + generationAttempts, + maxGenerationAttempts, +}: { + generationAttempts: number; + maxGenerationAttempts: number; +}): boolean => generationAttempts >= maxGenerationAttempts; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts new file mode 100644 index 0000000000000..b2c90636ef523 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/index.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { CompiledStateGraph } from '@langchain/langgraph'; +import { END, START, StateGraph } from '@langchain/langgraph'; + +import { NodeType } from './constants'; +import { getGenerateOrEndEdge } from './edges/generate_or_end'; +import { getGenerateOrRefineOrEndEdge } from './edges/generate_or_refine_or_end'; +import { getRefineOrEndEdge } from './edges/refine_or_end'; +import { getRetrieveAnonymizedAlertsOrGenerateEdge } from './edges/retrieve_anonymized_alerts_or_generate'; +import { getDefaultGraphState } from './state'; +import { getGenerateNode } from './nodes/generate'; +import { getRefineNode } from './nodes/refine'; +import { getRetrieveAnonymizedAlertsNode } from './nodes/retriever'; +import type { GraphState } from './types'; + +export interface GetDefaultAttackDiscoveryGraphParams { + alertsIndexPattern?: string; + anonymizationFields: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + llm: ActionsClientLlm; + logger?: Logger; + onNewReplacements?: (replacements: Replacements) => void; + replacements?: Replacements; + size: number; +} + +export type DefaultAttackDiscoveryGraph = ReturnType<typeof getDefaultAttackDiscoveryGraph>; + +/** + * This function returns a compiled state graph that represents the default + * Attack discovery graph. + * + * Refer to the following diagram for this graph: + * x-pack/plugins/elastic_assistant/docs/img/default_attack_discovery_graph.png + */ +export const getDefaultAttackDiscoveryGraph = ({ + alertsIndexPattern, + anonymizationFields, + esClient, + llm, + logger, + onNewReplacements, + replacements, + size, +}: GetDefaultAttackDiscoveryGraphParams): CompiledStateGraph< + GraphState, + Partial<GraphState>, + 'generate' | 'refine' | 'retrieve_anonymized_alerts' | '__start__' +> => { + try { + const graphState = getDefaultGraphState(); + + // get nodes: + const retrieveAnonymizedAlertsNode = getRetrieveAnonymizedAlertsNode({ + alertsIndexPattern, + anonymizationFields, + esClient, + logger, + onNewReplacements, + replacements, + size, + }); + + const generateNode = getGenerateNode({ + llm, + logger, + }); + + const refineNode = getRefineNode({ + llm, + logger, + }); + + // get edges: + const generateOrEndEdge = getGenerateOrEndEdge(logger); + + const generatOrRefineOrEndEdge = getGenerateOrRefineOrEndEdge(logger); + + const refineOrEndEdge = getRefineOrEndEdge(logger); + + const retrieveAnonymizedAlertsOrGenerateEdge = + getRetrieveAnonymizedAlertsOrGenerateEdge(logger); + + // create the graph: + const graph = new StateGraph<GraphState>({ channels: graphState }) + .addNode(NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, retrieveAnonymizedAlertsNode) + .addNode(NodeType.GENERATE_NODE, generateNode) + .addNode(NodeType.REFINE_NODE, refineNode) + .addConditionalEdges(START, retrieveAnonymizedAlertsOrGenerateEdge, { + generate: NodeType.GENERATE_NODE, + retrieve_anonymized_alerts: NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, + }) + .addConditionalEdges(NodeType.RETRIEVE_ANONYMIZED_ALERTS_NODE, generateOrEndEdge, { + end: END, + generate: NodeType.GENERATE_NODE, + }) + .addConditionalEdges(NodeType.GENERATE_NODE, generatOrRefineOrEndEdge, { + end: END, + generate: NodeType.GENERATE_NODE, + refine: NodeType.REFINE_NODE, + }) + .addConditionalEdges(NodeType.REFINE_NODE, refineOrEndEdge, { + end: END, + refine: NodeType.REFINE_NODE, + }); + + // compile the graph: + return graph.compile(); + } catch (e) { + throw new Error(`Unable to compile AttackDiscoveryGraph\n${e}`); + } +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/mock/mock_anonymization_fields.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields.ts similarity index 100% rename from x-pack/plugins/security_solution/server/assistant/tools/mock/mock_anonymization_fields.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields.ts diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts new file mode 100644 index 0000000000000..ed5549acc586a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_empty_open_and_acknowledged_alerts_qery_results.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const mockEmptyOpenAndAcknowledgedAlertsQueryResults = { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 0, + relation: 'eq', + }, + max_score: null, + hits: [], + }, +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts new file mode 100644 index 0000000000000..3f22f787f54f8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_open_and_acknowledged_alerts_query_results.ts @@ -0,0 +1,1396 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const mockOpenAndAcknowledgedAlertsQueryResults = { + took: 13, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 31, + relation: 'eq', + }, + max_score: null, + hits: [ + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['85caafe3d324e3287b85348fa2fae492'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!!', + ], + 'process.parent.name': ['unix1'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231', + ], + 'process.code_signature.signing_id': ['nans-55554944e5f232edcf023cf68e8e5dac81584f78'], + 'process.pid': [1227], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': ['/Users/james/unix1'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': ['/Users/james/unix1'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': [ + '/Users/james/unix1', + '/Users/james/library/Keychains/login.keychain-db', + 'TempTemp1234!!', + ], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [3], + 'process.name': ['unix1'], + 'process.parent.args': [ + '/Users/james/unix1', + '/Users/james/library/Keychains/login.keychain-db', + 'TempTemp1234!!', + ], + '@timestamp': ['2024-05-07T12:48:45.032Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': [ + '/Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!!', + ], + 'host.risk.calculated_level': ['High'], + _id: ['b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560'], + 'process.hash.sha1': ['4ca549355736e4af6434efc4ec9a044ceb2ae3c3'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:39.368Z'], + }, + sort: [99, 1715086125032], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1220], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + '@timestamp': ['2024-05-07T12:48:45.030Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'host.risk.calculated_level': ['High'], + _id: ['0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:38.061Z'], + }, + sort: [99, 1715086125030], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['85caafe3d324e3287b85348fa2fae492'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231', + ], + 'process.code_signature.signing_id': ['nans-55554944e5f232edcf023cf68e8e5dac81584f78'], + 'process.pid': [1220], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': ['/Users/james/unix1'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['unix1'], + 'process.parent.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + '@timestamp': ['2024-05-07T12:48:45.029Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'host.risk.calculated_level': ['High'], + _id: ['600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a'], + 'process.hash.sha1': ['4ca549355736e4af6434efc4ec9a044ceb2ae3c3'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:37.881Z'], + }, + sort: [99, 1715086125029], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['/Users/james/unix1'], + 'process.hash.md5': ['3f19892ab44eb9bc7bc03f438944301e'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.name': ['james'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'f80234ff6fed2c62d23f37443f2412fbe806711b6add2ac126e03e282082c8f5', + ], + 'process.code_signature.signing_id': ['com.apple.chmod'], + 'process.pid': [1219], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Software Signing'], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['0b18d6880dc9670ab2b955914598c96fc3d0097dc40ea61157b8c79e75edf231'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': ['/bin/chmod'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.parent.code_signature.subject_name': [''], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['unix1'], + 'process.args': ['chmod', '777', '/Users/james/unix1'], + 'process.code_signature.status': ['No error.'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['chmod'], + 'process.parent.args': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + '@timestamp': ['2024-05-07T12:48:45.028Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['chmod 777 /Users/james/unix1'], + 'host.risk.calculated_level': ['High'], + _id: ['e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c'], + 'process.hash.sha1': ['217490d4f51717aa3b301abec96be08602370d2d'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:37.869Z'], + }, + sort: [99, 1715086125028], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['643dddff1a57cbf70594854b44eb1a1d'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [73.02488], + 'rule.reference': [ + 'https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/prompt.py', + 'https://ss64.com/osx/osascript.html', + ], + 'process.parent.name': ['My Go Application.app'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'bab17feba710b469e5d96820f0cb7ed511d983e5817f374ec3cb46462ac5b794', + ], + 'process.pid': [1206], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Software Signing'], + 'host.os.version': ['13.4'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.72442], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': [ + 'Malicious Behavior Detection Alert: Potential Credentials Phishing via OSASCRIPT', + ], + 'host.name': ['SRVMAC08'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'group.name': ['staff'], + 'kibana.alert.workflow_status': ['open'], + 'rule.name': ['Potential Credentials Phishing via OSASCRIPT'], + 'threat.tactic.id': ['TA0006'], + 'threat.tactic.name': ['Credential Access'], + 'threat.technique.id': ['T1056'], + 'process.parent.args_count': [0], + 'threat.technique.subtechnique.reference': [ + 'https://attack.mitre.org/techniques/T1056/002/', + ], + 'process.name': ['osascript'], + 'threat.technique.subtechnique.name': ['GUI Input Capture'], + 'process.parent.code_signature.trusted': [false], + _id: ['2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f'], + 'threat.technique.name': ['Input Capture'], + 'group.id': ['20'], + 'threat.tactic.reference': ['https://attack.mitre.org/tactics/TA0006/'], + 'user.name': ['james'], + 'threat.framework': ['MITRE ATT&CK'], + 'process.code_signature.signing_id': ['com.apple.osascript'], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': [ + 'code failed to satisfy specified code requirement(s)', + ], + 'event.module': ['endpoint'], + 'process.executable': ['/usr/bin/osascript'], + 'process.parent.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.args': [ + 'osascript', + '-e', + 'display dialog "MacOS wants to access System Preferences\n\t\t\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬', + ], + 'process.code_signature.status': ['No error.'], + message: [ + 'Malicious Behavior Detection Alert: Potential Credentials Phishing via OSASCRIPT', + ], + '@timestamp': ['2024-05-07T12:48:45.027Z'], + 'threat.technique.subtechnique.id': ['T1056.002'], + 'threat.technique.reference': ['https://attack.mitre.org/techniques/T1056/'], + 'process.command_line': [ + 'osascript -e display dialog "MacOS wants to access System Preferences\n\t\t\nPlease enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬', + ], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['0568baae15c752208ae56d8f9c737976d6de2e3a'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:09.909Z'], + }, + sort: [99, 1715086125027], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '2a9f7602de8656d30dda0ddcf79e78037ac2929780e13d5b2047b3bedc40bb69', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['/sbin/launchd'], + 'process.parent.name': ['launchd'], + 'user.name': ['root'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1200], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['No error.'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.491455], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/6D63F08A-011C-4511-8556-EAEF9AFD6340/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': ['Software Signing'], + 'process.parent.executable': ['/sbin/launchd'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['My Go Application.app'], + 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': ['/sbin/launchd'], + '@timestamp': ['2024-05-07T12:48:45.023Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', + ], + 'host.risk.calculated_level': ['High'], + _id: ['2a9f7602de8656d30dda0ddcf79e78037ac2929780e13d5b2047b3bedc40bb69'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:28:06.888Z'], + }, + sort: [99, 1715086125023], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '4615c3a90e8057ae5cc9b358bbbf4298e346277a2f068dda052b0b43ef6d5bbd', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/3C4D44B9-4838-4613-BACC-BD00A9CE4025/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['/sbin/launchd'], + 'process.parent.name': ['launchd'], + 'user.name': ['root'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1169], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['No error.'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.491455], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/3C4D44B9-4838-4613-BACC-BD00A9CE4025/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': ['Software Signing'], + 'process.parent.executable': ['/sbin/launchd'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['My Go Application.app'], + 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': ['/sbin/launchd'], + '@timestamp': ['2024-05-07T12:48:45.022Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', + ], + 'host.risk.calculated_level': ['High'], + _id: ['4615c3a90e8057ae5cc9b358bbbf4298e346277a2f068dda052b0b43ef6d5bbd'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:27:47.362Z'], + }, + sort: [99, 1715086125022], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '449322a72d3f19efbdf983935a1bdd21ebd6b9c761ce31e8b252003017d7e5db', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/37D933EC-334D-410A-A741-0F730D6AE3FD/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'process.hash.md5': ['e62bdd3eaf2be436fca2e67b7eede603'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['/sbin/launchd'], + 'process.parent.name': ['launchd'], + 'user.name': ['root'], + 'user.risk.calculated_level': ['Moderate'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097', + ], + 'process.code_signature.signing_id': ['a.out'], + 'process.pid': [1123], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['No error.'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': [''], + 'host.os.version': ['13.4'], + 'file.hash.sha256': ['2c63ba2b1a5131b80e567b7a1a93997a2de07ea20d0a8f5149701c67b832c097'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [66.491455], + 'host.os.name': ['macOS'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVMAC08'], + 'process.executable': [ + '/private/var/folders/_b/rmcpc65j6nv11ygrs50ctcjr0000gn/T/AppTranslocation/37D933EC-334D-410A-A741-0F730D6AE3FD/d/Setup.app/Contents/MacOS/My Go Application.app', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.parent.code_signature.subject_name': ['Software Signing'], + 'process.parent.executable': ['/sbin/launchd'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['My Go Application.app'], + 'process.args': ['xpcproxy', 'application.Appify by Machine Box.My Go Application.20.23'], + 'process.code_signature.status': ['code failed to satisfy specified code requirement(s)'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['My Go Application.app'], + 'process.parent.args': ['/sbin/launchd'], + '@timestamp': ['2024-05-07T12:48:45.020Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + 'xpcproxy application.Appify by Machine Box.My Go Application.20.23', + ], + 'host.risk.calculated_level': ['High'], + _id: ['449322a72d3f19efbdf983935a1bdd21ebd6b9c761ce31e8b252003017d7e5db'], + 'process.hash.sha1': ['58a3bddbc7c45193ecbefa22ad0496b60a29dff2'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-06-19T00:25:24.716Z'], + }, + sort: [99, 1715086125020], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'f465ca9fbfc8bc3b1871e965c9e111cac76ff3f4076fed6bc9da88d49fb43014', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Shellcode Injection'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:45.017Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['f465ca9fbfc8bc3b1871e965c9e111cac76ff3f4076fed6bc9da88d49fb43014'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.051Z'], + }, + sort: [99, 1715086125017], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'aa283e6a13be77b533eceffb09e48254c8f91feeccc39f7eed80fd3881d053f4', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['C:\\Windows\\mpsvc.dll'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection', 'library'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['8dd620d9aeb35960bb766458c8890ede987c33d239cf730f93fe49d90ae759dd'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['mpsvc.dll'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:45.008Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['aa283e6a13be77b533eceffb09e48254c8f91feeccc39f7eed80fd3881d053f4'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:18.093Z'], + }, + sort: [99, 1715086125008], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'dd9e4ea23961ccfdb7a9c760ee6bedd19a013beac3b0d38227e7ae77ba4ce515', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['C:\\Windows\\mpsvc.dll'], + 'process.hash.md5': ['561cffbaba71a6e8cc1cdceda990ead4'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': ['C:\\Windows\\Explorer.EXE'], + 'process.parent.name': ['explorer.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', + ], + 'process.pid': [1008], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['8dd620d9aeb35960bb766458c8890ede987c33d239cf730f93fe49d90ae759dd'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\explorer.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['mpsvc.dll'], + 'process.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.code_signature.status': ['errorExpired'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.parent.args': ['C:\\Windows\\Explorer.EXE'], + '@timestamp': ['2024-05-07T12:48:45.007Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'host.risk.calculated_level': ['High'], + _id: ['dd9e4ea23961ccfdb7a9c760ee6bedd19a013beac3b0d38227e7ae77ba4ce515'], + 'process.hash.sha1': ['5162f14d75e96edb914d1756349d6e11583db0b0'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:17.887Z'], + }, + sort: [99, 1715086125007], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'f30d55e503b1d848b34ee57741b203d8052360dd873ea34802f3fa7a9ef34d0a', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.hash.md5': ['561cffbaba71a6e8cc1cdceda990ead4'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': ['C:\\Windows\\Explorer.EXE'], + 'process.parent.name': ['explorer.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', + ], + 'process.pid': [1008], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [false], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\explorer.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.code_signature.status': ['errorExpired'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.parent.args': ['C:\\Windows\\Explorer.EXE'], + '@timestamp': ['2024-05-07T12:48:45.006Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'host.risk.calculated_level': ['High'], + _id: ['f30d55e503b1d848b34ee57741b203d8052360dd873ea34802f3fa7a9ef34d0a'], + 'process.hash.sha1': ['5162f14d75e96edb914d1756349d6e11583db0b0'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:17.544Z'], + }, + sort: [99, 1715086125006], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '6f8cd5e8021dbb64598f2b7ec56bee21fd00d1e62d4e08905f86bf234873ee66', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.hash.md5': ['f070b5cf25febb9a88a168efd87c6112'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [''], + 'process.parent.name': ['userinit.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '567be4d1e15f4ff96d92e7d28e191076f5813f50be96bf4c3916e4ecf53f66cd', + ], + 'process.pid': [6228], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'process.pe.original_file_name': ['EXPLORER.EXE'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\explorer.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\System32\\userinit.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe'], + 'process.args': ['C:\\Windows\\Explorer.EXE'], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.name': ['explorer.exe'], + '@timestamp': ['2024-05-07T12:48:45.004Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': ['C:\\Windows\\Explorer.EXE'], + 'host.risk.calculated_level': ['High'], + _id: ['6f8cd5e8021dbb64598f2b7ec56bee21fd00d1e62d4e08905f86bf234873ee66'], + 'process.hash.sha1': ['94518c310478e494082418ed295466f5aea26eea'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:37:18.152Z'], + }, + sort: [99, 1715086125004], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'ce110da958fe0cf0c07599a21c68d90a64c93b7607aa27970a614c7f49598316', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e', + ], + 'process.hash.md5': ['f070b5cf25febb9a88a168efd87c6112'], + 'event.category': ['malware', 'intrusion_detection', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [''], + 'process.parent.name': ['userinit.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '567be4d1e15f4ff96d92e7d28e191076f5813f50be96bf4c3916e4ecf53f66cd', + ], + 'process.pid': [6228], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['trusted'], + 'process.pe.original_file_name': ['EXPLORER.EXE'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\explorer.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['Microsoft Windows'], + 'process.parent.executable': ['C:\\Windows\\System32\\userinit.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e'], + 'process.args': ['C:\\Windows\\Explorer.EXE'], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.name': ['explorer.exe'], + '@timestamp': ['2024-05-07T12:48:45.001Z'], + 'process.parent.code_signature.trusted': [true], + 'process.command_line': ['C:\\Windows\\Explorer.EXE'], + 'host.risk.calculated_level': ['High'], + _id: ['ce110da958fe0cf0c07599a21c68d90a64c93b7607aa27970a614c7f49598316'], + 'process.hash.sha1': ['94518c310478e494082418ed295466f5aea26eea'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:36:43.813Z'], + }, + sort: [99, 1715086125001], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '0866787b0027b4d908767ac16e35a1da00970c83632ba85be65f2ad371132b4f', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection', 'process', 'file'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Ransomware Detection Alert'], + 'host.name': ['SRVWIN02'], + 'Ransomware.files.data': [ + '2D002D002D003D003D003D0020005700', + '2D002D002D003D003D003D0020005700', + '2D002D002D003D003D003D0020005700', + ], + 'process.code_signature.trusted': [true], + 'Ransomware.files.metrics': ['CANARY_ACTIVITY'], + 'kibana.alert.workflow_status': ['open'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'Ransomware.files.score': [0, 0, 0], + 'process.parent.code_signature.trusted': [false], + _id: ['0866787b0027b4d908767ac16e35a1da00970c83632ba85be65f2ad371132b4f'], + 'Ransomware.version': ['1.6.0'], + 'user.name': ['Administrator'], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'Ransomware.files.operation': ['creation', 'creation', 'creation'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.Ext.token.integrity_level_name': ['high'], + 'Ransomware.files.path': [ + 'c:\\hd3vuk19y-readme.txt', + 'c:\\$winreagent\\hd3vuk19y-readme.txt', + 'c:\\aaantiransomelastic-do-not-touch-dab6d40c-a6a1-442c-adc4-9d57a47e58d7\\hd3vuk19y-readme.txt', + ], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'Ransomware.files.entropy': [3.629971457026797, 3.629971457026797, 3.629971457026797], + 'Ransomware.feature': ['canary'], + 'Ransomware.files.extension': ['txt', 'txt', 'txt'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Ransomware Detection Alert'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:45.000Z'], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.964Z'], + }, + sort: [99, 1715086125000], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'b0fdf96721e361e1137d49a67e26d92f96b146392d7f44322bddc3d660abaef1', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Shellcode Injection'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:44.996Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['b0fdf96721e361e1137d49a67e26d92f96b146392d7f44322bddc3d660abaef1'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.174Z'], + }, + sort: [99, 1715086124996], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '7b4f49f21cf141e67856d3207fb4ea069c8035b41f0ea501970694cf8bd43cbe', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Memory Threat Detection Alert: Shellcode Injection'], + 'host.name': ['SRVWIN02'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'kibana.alert.workflow_status': ['open'], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Shellcode Injection'], + 'process.parent.args_count': [1], + 'process.name': ['MsMpEng.exe'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:44.986Z'], + 'process.parent.code_signature.trusted': [false], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + _id: ['7b4f49f21cf141e67856d3207fb4ea069c8035b41f0ea501970694cf8bd43cbe'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:22.066Z'], + }, + sort: [99, 1715086124986], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'ea81d79104cbd442236b5bcdb7a3331de897aa4ce1523e622068038d048d0a9e', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['8cc83221870dd07144e63df594c391d9'], + 'event.category': ['malware', 'intrusion_detection', 'process'], + 'host.risk.calculated_score_norm': [75.62723], + 'process.parent.command_line': [ + '"C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe" ', + ], + 'process.parent.name': [ + 'd55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '33bc14d231a4afaa18f06513766d5f69d8b88f1e697cd127d24fb4b72ad44c7a', + ], + 'process.Ext.memory_region.malware_signature.primary.matches': [ + 'WVmF9nQli1UIg2YEAIk+iwoLSgQ=', + 'dQxy0zPAQF9eW4vlXcMzwOv1VYvsgw==', + 'DIsEsIN4BAV1HP9wCP9wDP91DP8=', + '+4tF/FCLCP9RCF6Lx19bi+Vdw1U=', + 'vAAAADPSi030i/GLRfAPpMEBwe4f', + 'VIvO99GLwiNN3PfQM030I8czReiJ', + 'DIlGDIXAdSozwOtsi0YIhcB0Yms=', + ], + 'process.pid': [8708], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Microsoft Corporation'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': [ + 'Memory Threat Detection Alert: Windows.Ransomware.Sodinokibi', + ], + 'host.name': ['SRVWIN02'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'kibana.alert.workflow_status': ['open'], + 'rule.name': ['Windows.Ransomware.Sodinokibi'], + 'process.parent.args_count': [1], + 'process.Ext.memory_region.bytes_compressed_present': [false], + 'process.name': ['MsMpEng.exe'], + 'process.parent.code_signature.trusted': [false], + _id: ['ea81d79104cbd442236b5bcdb7a3331de897aa4ce1523e622068038d048d0a9e'], + 'user.name': ['Administrator'], + 'process.parent.code_signature.exists': [true], + 'process.parent.code_signature.status': ['errorExpired'], + 'process.pe.original_file_name': ['MsMpEng.exe'], + 'event.module': ['endpoint'], + 'process.Ext.memory_region.malware_signature.all_names': [ + 'Windows.Ransomware.Sodinokibi', + ], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\MsMpEng.exe'], + 'process.Ext.memory_region.malware_signature.primary.signature.name': [ + 'Windows.Ransomware.Sodinokibi', + ], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.code_signature.subject_name': ['PB03 TRANSPORT LTD.'], + 'process.parent.executable': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + 'process.args': ['C:\\Windows\\MsMpEng.exe'], + 'process.code_signature.status': ['trusted'], + message: ['Memory Threat Detection Alert: Windows.Ransomware.Sodinokibi'], + 'process.parent.args': [ + 'C:\\Users\\Administrator\\Desktop\\8813719803\\d55f983c994caa160ec63a59f6b4250fe67fb3e8c43a388aec60a4a6978e9f1e.exe', + ], + '@timestamp': ['2024-05-07T12:48:44.975Z'], + 'process.command_line': ['"C:\\Windows\\MsMpEng.exe"'], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['3d409b39b8502fcd23335a878f2cbdaf6d721995'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-20T23:38:25.169Z'], + }, + sort: [99, 1715086124975], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'cdf3b5510bb5ed622e8cefd1ce6bedc52bdd99a4c1ead537af0603469e713c8b', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'file.path': ['C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll'], + 'process.hash.md5': ['4bfef0b578515c16b9582e32b78d2594'], + 'event.category': ['malware', 'intrusion_detection', 'library'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['C:\\Programdata\\Q3C7N1V8.exe'], + 'process.parent.name': ['Q3C7N1V8.exe'], + 'user.name': ['Administrator'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '70d21cbdc527559c4931421e66aa819b86d5af5535445ace467e74518164c46a', + ], + 'process.pid': [7824], + 'process.code_signature.exists': [true], + 'process.parent.code_signature.exists': [false], + 'process.pe.original_file_name': ['RUNDLL32.EXE'], + 'event.module': ['endpoint'], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'file.hash.sha256': ['12e6642cf6413bdf5388bee663080fa299591b2ba023d069286f3be9647547c8'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': ['Malware Detection Alert'], + 'host.name': ['SRVWIN01'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\SysWOW64\\rundll32.exe'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.executable': ['C:\\ProgramData\\Q3C7N1V8.exe'], + 'kibana.alert.workflow_status': ['open'], + 'file.name': ['cdnver.dll'], + 'process.args': [ + 'C:\\Windows\\System32\\rundll32.exe', + 'C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll,#1', + ], + 'process.code_signature.status': ['trusted'], + message: ['Malware Detection Alert'], + 'process.parent.args_count': [1], + 'process.name': ['rundll32.exe'], + 'process.parent.args': ['C:\\Programdata\\Q3C7N1V8.exe'], + '@timestamp': ['2024-05-07T12:47:32.838Z'], + 'process.command_line': [ + '"C:\\Windows\\System32\\rundll32.exe" "C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll",#1', + ], + 'host.risk.calculated_level': ['High'], + _id: ['cdf3b5510bb5ed622e8cefd1ce6bedc52bdd99a4c1ead537af0603469e713c8b'], + 'process.hash.sha1': ['9b16507aaf10a0aafa0df2ba83e8eb2708d83a02'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-16T01:51:26.472Z'], + }, + sort: [99, 1715086052838], + }, + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '6abe81eb6350fb08031761be029e7ab19f7e577a7c17a9c5ea1ed010ba1620e3', + _score: null, + fields: { + 'kibana.alert.severity': ['critical'], + 'process.hash.md5': ['4bfef0b578515c16b9582e32b78d2594'], + 'event.category': ['malware', 'intrusion_detection'], + 'host.risk.calculated_score_norm': [73.02488], + 'process.parent.command_line': ['C:\\Programdata\\Q3C7N1V8.exe'], + 'process.parent.name': ['Q3C7N1V8.exe'], + 'user.risk.calculated_level': ['High'], + 'kibana.alert.rule.description': [ + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + ], + 'process.hash.sha256': [ + '70d21cbdc527559c4931421e66aa819b86d5af5535445ace467e74518164c46a', + ], + 'process.pid': [7824], + 'process.code_signature.exists': [true], + 'process.code_signature.subject_name': ['Microsoft Windows'], + 'host.os.version': ['21H2 (10.0.20348.1366)'], + 'kibana.alert.risk_score': [99], + 'user.risk.calculated_score_norm': [82.16188], + 'host.os.name': ['Windows'], + 'kibana.alert.rule.name': [ + 'Malicious Behavior Detection Alert: RunDLL32 with Unusual Arguments', + ], + 'host.name': ['SRVWIN01'], + 'event.outcome': ['success'], + 'process.code_signature.trusted': [true], + 'kibana.alert.workflow_status': ['open'], + 'rule.name': ['RunDLL32 with Unusual Arguments'], + 'threat.tactic.id': ['TA0005'], + 'threat.tactic.name': ['Defense Evasion'], + 'threat.technique.id': ['T1218'], + 'process.parent.args_count': [1], + 'threat.technique.subtechnique.reference': [ + 'https://attack.mitre.org/techniques/T1218/011/', + ], + 'process.name': ['rundll32.exe'], + 'threat.technique.subtechnique.name': ['Rundll32'], + _id: ['6abe81eb6350fb08031761be029e7ab19f7e577a7c17a9c5ea1ed010ba1620e3'], + 'threat.technique.name': ['System Binary Proxy Execution'], + 'threat.tactic.reference': ['https://attack.mitre.org/tactics/TA0005/'], + 'user.name': ['Administrator'], + 'threat.framework': ['MITRE ATT&CK'], + 'process.working_directory': ['C:\\Users\\Administrator\\Documents\\'], + 'process.pe.original_file_name': ['RUNDLL32.EXE'], + 'event.module': ['endpoint'], + 'user.domain': ['OMM-WIN-DETECT'], + 'process.executable': ['C:\\Windows\\SysWOW64\\rundll32.exe'], + 'process.Ext.token.integrity_level_name': ['high'], + 'process.parent.executable': ['C:\\ProgramData\\Q3C7N1V8.exe'], + 'process.args': [ + 'C:\\Windows\\System32\\rundll32.exe', + 'C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll,#1', + ], + 'process.code_signature.status': ['trusted'], + message: ['Malicious Behavior Detection Alert: RunDLL32 with Unusual Arguments'], + 'process.parent.args': ['C:\\Programdata\\Q3C7N1V8.exe'], + '@timestamp': ['2024-05-07T12:47:32.836Z'], + 'threat.technique.subtechnique.id': ['T1218.011'], + 'threat.technique.reference': ['https://attack.mitre.org/techniques/T1218/'], + 'process.command_line': [ + '"C:\\Windows\\System32\\rundll32.exe" "C:\\Users\\Administrator\\AppData\\Local\\cdnver.dll",#1', + ], + 'host.risk.calculated_level': ['High'], + 'process.hash.sha1': ['9b16507aaf10a0aafa0df2ba83e8eb2708d83a02'], + 'event.dataset': ['endpoint.alerts'], + 'kibana.alert.original_time': ['2023-01-16T01:51:26.348Z'], + }, + sort: [99, 1715086052836], + }, + ], + }, +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts new file mode 100644 index 0000000000000..a40dde44f8d67 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/discard_previous_generations/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { GraphState } from '../../../../types'; + +export const discardPreviousGenerations = ({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected, + state, +}: { + generationAttempts: number; + hallucinationFailures: number; + isHallucinationDetected: boolean; + state: GraphState; +}): GraphState => { + return { + ...state, + combinedGenerations: '', // <-- reset the combined generations + generationAttempts: generationAttempts + 1, + generations: [], // <-- reset the generations + hallucinationFailures: isHallucinationDetected + ? hallucinationFailures + 1 + : hallucinationFailures, + }; +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts similarity index 70% rename from x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts index bc290bf172382..287f5e6b2130a 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.test.ts @@ -4,15 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getAttackDiscoveryPrompt } from './get_attack_discovery_prompt'; -describe('getAttackDiscoveryPrompt', () => { - it('should generate the correct attack discovery prompt', () => { +import { getAlertsContextPrompt } from '.'; +import { getDefaultAttackDiscoveryPrompt } from '../../../helpers/get_default_attack_discovery_prompt'; + +describe('getAlertsContextPrompt', () => { + it('generates the correct prompt', () => { const anonymizedAlerts = ['Alert 1', 'Alert 2', 'Alert 3']; - const expected = `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. + const expected = `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. You MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds). -Use context from the following open and acknowledged alerts to provide insights: +Use context from the following alerts to provide insights: """ Alert 1 @@ -23,7 +25,10 @@ Alert 3 """ `; - const prompt = getAttackDiscoveryPrompt({ anonymizedAlerts }); + const prompt = getAlertsContextPrompt({ + anonymizedAlerts, + attackDiscoveryPrompt: getDefaultAttackDiscoveryPrompt(), + }); expect(prompt).toEqual(expected); }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/index.ts new file mode 100644 index 0000000000000..d92d935053577 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_alerts_context_prompt/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. + */ + +// NOTE: we ask the LLM to `provide insights`. We do NOT use the feature name, `AttackDiscovery`, in the prompt. +export const getAlertsContextPrompt = ({ + anonymizedAlerts, + attackDiscoveryPrompt, +}: { + anonymizedAlerts: string[]; + attackDiscoveryPrompt: string; +}) => `${attackDiscoveryPrompt} + +Use context from the following alerts to provide insights: + +""" +${anonymizedAlerts.join('\n\n')} +""" +`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts new file mode 100644 index 0000000000000..fb7cf6bd59f98 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_anonymized_alerts_from_state/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GraphState } from '../../../../types'; + +export const getAnonymizedAlertsFromState = (state: GraphState): string[] => + state.anonymizedAlerts.map((doc) => doc.pageContent); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts new file mode 100644 index 0000000000000..face2a6afc6bc --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/helpers/get_use_unrefined_results/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { AttackDiscovery } from '@kbn/elastic-assistant-common'; + +import { getMaxRetriesReached } from '../../../../helpers/get_max_retries_reached'; + +export const getUseUnrefinedResults = ({ + generationAttempts, + maxGenerationAttempts, + unrefinedResults, +}: { + generationAttempts: number; + maxGenerationAttempts: number; + unrefinedResults: AttackDiscovery[] | null; +}): boolean => { + const nextAttemptWouldExcedLimit = getMaxRetriesReached({ + generationAttempts: generationAttempts + 1, // + 1, because we just used an attempt + maxGenerationAttempts, + }); + + return nextAttemptWouldExcedLimit && unrefinedResults != null && unrefinedResults.length > 0; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts new file mode 100644 index 0000000000000..1fcd81622f0fe --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/index.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { Logger } from '@kbn/core/server'; + +import { discardPreviousGenerations } from './helpers/discard_previous_generations'; +import { extractJson } from '../helpers/extract_json'; +import { getAnonymizedAlertsFromState } from './helpers/get_anonymized_alerts_from_state'; +import { getChainWithFormatInstructions } from '../helpers/get_chain_with_format_instructions'; +import { getCombined } from '../helpers/get_combined'; +import { getCombinedAttackDiscoveryPrompt } from '../helpers/get_combined_attack_discovery_prompt'; +import { generationsAreRepeating } from '../helpers/generations_are_repeating'; +import { getUseUnrefinedResults } from './helpers/get_use_unrefined_results'; +import { parseCombinedOrThrow } from '../helpers/parse_combined_or_throw'; +import { responseIsHallucinated } from '../helpers/response_is_hallucinated'; +import type { GraphState } from '../../types'; + +export const getGenerateNode = ({ + llm, + logger, +}: { + llm: ActionsClientLlm; + logger?: Logger; +}): ((state: GraphState) => Promise<GraphState>) => { + const generate = async (state: GraphState): Promise<GraphState> => { + logger?.debug(() => `---GENERATE---`); + + const anonymizedAlerts: string[] = getAnonymizedAlertsFromState(state); + + const { + attackDiscoveryPrompt, + combinedGenerations, + generationAttempts, + generations, + hallucinationFailures, + maxGenerationAttempts, + maxRepeatedGenerations, + } = state; + + let combinedResponse = ''; // mutable, because it must be accessed in the catch block + let partialResponse = ''; // mutable, because it must be accessed in the catch block + + try { + const query = getCombinedAttackDiscoveryPrompt({ + anonymizedAlerts, + attackDiscoveryPrompt, + combinedMaybePartialResults: combinedGenerations, + }); + + const { chain, formatInstructions, llmType } = getChainWithFormatInstructions(llm); + + logger?.debug( + () => `generate node is invoking the chain (${llmType}), attempt ${generationAttempts}` + ); + + const rawResponse = (await chain.invoke({ + format_instructions: formatInstructions, + query, + })) as unknown as string; + + // LOCAL MUTATION: + partialResponse = extractJson(rawResponse); // remove the surrounding ```json``` + + // if the response is hallucinated, discard previous generations and start over: + if (responseIsHallucinated(partialResponse)) { + logger?.debug( + () => + `generate node detected a hallucination (${llmType}), on attempt ${generationAttempts}; discarding the accumulated generations and starting over` + ); + + return discardPreviousGenerations({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: true, + state, + }); + } + + // if the generations are repeating, discard previous generations and start over: + if ( + generationsAreRepeating({ + currentGeneration: partialResponse, + previousGenerations: generations, + sampleLastNGenerations: maxRepeatedGenerations, + }) + ) { + logger?.debug( + () => + `generate node detected (${llmType}), detected ${maxRepeatedGenerations} repeated generations on attempt ${generationAttempts}; discarding the accumulated results and starting over` + ); + + // discard the accumulated results and start over: + return discardPreviousGenerations({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: false, + state, + }); + } + + // LOCAL MUTATION: + combinedResponse = getCombined({ combinedGenerations, partialResponse }); // combine the new response with the previous ones + + const unrefinedResults = parseCombinedOrThrow({ + combinedResponse, + generationAttempts, + llmType, + logger, + nodeName: 'generate', + }); + + // use the unrefined results if we already reached the max number of retries: + const useUnrefinedResults = getUseUnrefinedResults({ + generationAttempts, + maxGenerationAttempts, + unrefinedResults, + }); + + if (useUnrefinedResults) { + logger?.debug( + () => + `generate node is using unrefined results response (${llm._llmType()}) from attempt ${generationAttempts}, because all attempts have been used` + ); + } + + return { + ...state, + attackDiscoveries: useUnrefinedResults ? unrefinedResults : null, // optionally skip the refinement step by returning the final answer + combinedGenerations: combinedResponse, + generationAttempts: generationAttempts + 1, + generations: [...generations, partialResponse], + unrefinedResults, + }; + } catch (error) { + const parsingError = `generate node is unable to parse (${llm._llmType()}) response from attempt ${generationAttempts}; (this may be an incomplete response from the model): ${error}`; + logger?.debug(() => parsingError); // logged at debug level because the error is expected when the model returns an incomplete response + + return { + ...state, + combinedGenerations: combinedResponse, + errors: [...state.errors, parsingError], + generationAttempts: generationAttempts + 1, + generations: [...generations, partialResponse], + }; + } + }; + + return generate; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts new file mode 100644 index 0000000000000..05210799f151c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/generate/schema/index.ts @@ -0,0 +1,84 @@ +/* + * 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. + */ + +/* + * 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 { z } from '@kbn/zod'; + +export const SYNTAX = '{{ field.name fieldValue1 fieldValue2 fieldValueN }}'; +const GOOD_SYNTAX_EXAMPLES = + 'Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }}'; + +const BAD_SYNTAX_EXAMPLES = + 'Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}'; + +const RECONNAISSANCE = 'Reconnaissance'; +const INITIAL_ACCESS = 'Initial Access'; +const EXECUTION = 'Execution'; +const PERSISTENCE = 'Persistence'; +const PRIVILEGE_ESCALATION = 'Privilege Escalation'; +const DISCOVERY = 'Discovery'; +const LATERAL_MOVEMENT = 'Lateral Movement'; +const COMMAND_AND_CONTROL = 'Command and Control'; +const EXFILTRATION = 'Exfiltration'; + +const MITRE_ATTACK_TACTICS = [ + RECONNAISSANCE, + INITIAL_ACCESS, + EXECUTION, + PERSISTENCE, + PRIVILEGE_ESCALATION, + DISCOVERY, + LATERAL_MOVEMENT, + COMMAND_AND_CONTROL, + EXFILTRATION, +] as const; + +export const AttackDiscoveriesGenerationSchema = z.object({ + insights: z + .array( + z.object({ + alertIds: z.string().array().describe(`The alert IDs that the insight is based on.`), + detailsMarkdown: z + .string() + .describe( + `A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` + ), + entitySummaryMarkdown: z + .string() + .optional() + .describe( + `A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same ${SYNTAX} syntax` + ), + mitreAttackTactics: z + .string() + .array() + .optional() + .describe( + `An array of MITRE ATT&CK tactic for the insight, using one of the following values: ${MITRE_ATTACK_TACTICS.join( + ',' + )}` + ), + summaryMarkdown: z + .string() + .describe(`A markdown summary of insight, using the same ${SYNTAX} syntax`), + title: z + .string() + .describe( + 'A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.' + ), + }) + ) + .describe( + `Insights with markdown that always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` + ), +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts new file mode 100644 index 0000000000000..fd824709f5fcf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/add_trailing_backticks_if_necessary/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const addTrailingBackticksIfNecessary = (text: string): string => { + const leadingJSONpattern = /^\w*```json(.*?)/s; + const trailingBackticksPattern = /(.*?)```\w*$/s; + + const hasLeadingJSONWrapper = leadingJSONpattern.test(text); + const hasTrailingBackticks = trailingBackticksPattern.test(text); + + if (hasLeadingJSONWrapper && !hasTrailingBackticks) { + return `${text}\n\`\`\``; + } + + return text; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts new file mode 100644 index 0000000000000..5e13ec9f0dafe --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { extractJson } from '.'; + +describe('extractJson', () => { + it('returns the JSON text surrounded by ```json and ``` with no whitespace or additional text', () => { + const input = '```json{"key": "value"}```'; + + const expected = '{"key": "value"}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('returns the JSON block when surrounded by additional text and whitespace', () => { + const input = + 'You asked for some JSON, here it is:\n```json\n{"key": "value"}\n```\nI hope that works for you.'; + + const expected = '{"key": "value"}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('returns the original text if no JSON block is found', () => { + const input = "There's no JSON here, just some text."; + + expect(extractJson(input)).toBe(input); + }); + + it('trims leading and trailing whitespace from the extracted JSON', () => { + const input = 'Text before\n```json\n {"key": "value"} \n```\nText after'; + + const expected = '{"key": "value"}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('handles incomplete JSON blocks with no trailing ```', () => { + const input = 'Text before\n```json\n{"key": "value"'; // <-- no closing ```, because incomplete generation + + expect(extractJson(input)).toBe('{"key": "value"'); + }); + + it('handles multiline json (real world edge case)', () => { + const input = + '```json\n{\n "insights": [\n {\n "alertIds": [\n "a609473a23b3a66a40f2bba06795c28a0c12863c6931f39e472d069f5600cbae",\n "04a9ded2b4f10ea407711f0010d426ad328eea43ae53e1e0bf166c058947dff6",\n "8d53b9838181299b3c0b1544ea469216d72ad2234a1cce44017dd248a08d78d1",\n "51d0080ffcc1982dbae7c31a9a021f7b51422000dec1f0e0bb58bd61d934c893",\n "d93302956bee58d538f6f7a6cbf944e549e8466dacfb554a302dce46a069eef0",\n "75c89f679397f089716034cde20f5547a2e6bdd1606b1e002e0976ab339c4cd9",\n "5d8e9427c0ecc4daa5809bfe250b9a382c53e81e8f39eec87499d28efdda9300",\n "f18ac1874f510fd3fabb0ae48d0714f4952b294496ef1d993e3eb03f839e2d83",\n "e37cb31213c4c4e80beaf9f75e7966f88cdd86a228c6cb1a28e46356410fa78f",\n "cf70077b8888e8fbe434808fddbaf65d97fff244bb185a595cf0ad487e9c5850",\n "01bea609f0880b10b7b3c6cf6e8245ef0f134386fdcbf2a167e72487e0bda616",\n "289621edc88fd8b4775c541e46bcfdea40538291266179c59a5ca5afbee74cfc",\n "ba121c2045058b62a92e6a3abadd3c78a005b89129630e2271b2f45d5fd995b2",\n "fceb940b252be079df3629550d852bd2793f79071c917227268fa1b805abc8d1",\n "7044589c27bab148cdb97d9e2eeb88bd924fca82a6a05a53ec94dcadf8e56303",\n "1b68be35429f52280456aab17dd94191fe5c47fed9768f00d9f9e9044a08bbb5",\n "52478d4a119bbc44bec67f384f83dfa20b33cf9963177e619cd47a32bababe12",\n "fecbbb8924493b466e8f5744e0875a9ee91f326213b691b576b13da3fb875ebf",\n "c46bbdeb7b59f52c976e7e4f30e3d5c65f417d716cb140096b5edba52b1449a1",\n "f12caebcbda087fc8b49cdced64a8997dd1428f4cf91ebb251434a55126399b3",\n "c7478edbd13af443cfafc57d50e5206c5ae8c0f9c9cabc073fdc2d3946559617",\n "3585ae62651929ef405f9783410d7a94f4254d299205e22f22966178f189bb11",\n "f50f531912af1d31a66a0e37d4c0d9c571c2cca6bef2c7f8453eb3ab67c4d1a0",\n "95a9403f0bb97d03fc3c2eb06386503831766f541b736468088092c5e0e25830",\n "c1292c67f3ccd2cb2651c601f0816122cfa459276fa5fc89b40c62d1a793963e",\n "8911886e1b2964176f70eaee2aa6693ce101ee9c8ec5434acdc7ff18616ec31c",\n "bfbfb02c03c6f69fc2352c48d8fd7c7e4b557c611e16956fbb63e337a513e699",\n "064cbdc1932029fcb34f6ba685211b971afde3b8aa4325054bedaf4c9e4587ed",\n "9fd5d0ca9b9fff6e37f1114ad874103badb2b0570ef143cd4a26a553effdff00",\n "9e2687f26f04b5a8def3266f89fbe7217da2d4355c3b035268df1802f1342c81",\n "64557c4006c52119c01f6e3e582ce1b8207b2e8f64aaaa630ca1fd156c01ea1e",\n "df98d2568c986d101af055f78c7e2a39299627531c28012b5025d10e2ec1b208",\n "10683db11fb21cae36577f83722c686c2fc691d2be6fc4396f2733564f3210d1",\n "f46e7b3266200e3e23b15b5acea7bb934e2c17d23058e10daeed51f036f4932b",\n "3c77d55f912b80b66cc1e4e1df02a22ddee07c50338a409374fb2567d2fb4ca3",\n "8ec169c0fdf558c0d9d9ad8dedad0898b15bb718421b4cab8f5cce4ebcb78254",\n "4119a1705f993588f8d1d576e567ec17f102aeafe535e53bb56ec833418ccd08",\n "b53d06bfd23ab843dba67e2fde0da6364475b0bfb9c40cb8a6641cc4ecadec01",\n "1dcd85c8279fd7152dadecfc547cce06261d23ef4589fe4fdcc92b1ceeb76c0f",\n "d4ed490b1d39925ee612058655030bdb7cecda3e5893e1c40dbbac852b72fbc6",\n "2ecc96c4d51f5338684c08e7c67357e504abfec6fc4f21753a3c941189db68e1",\n "0c9fb123686bc739d117ee4f607ffbcef39f1f72e7eab6d01b70bbb40480b3d6",\n "162be5e04f54a5cd475d2437fe769ee044324b0a32ce83a735f61719b8b5fd63",\n "21eae60b4b29f7f01cc7006372374e1c5d6912858c33397cdbe4470df97fba79",\n "0409539590b6d9b80f7071d3d5658434f982ba7957aa6a5037f8b7a73b70100d",\n "5e8e654df34a9053f8b90e4ade25520dbee5994ebf7da531e1e7255d029ab031",\n "3ef381b2d29d71bc3ac8580d333344948a2664855a89ff037299a8b4aa663293",\n "0aef1fe2506842f9c53549049b47a8166bcc3d6efe2d8fcf1e57f3a634ed137c",\n "c2d12dacd0cd6ef4a7386c8d0146d3eb91a7e1e9f2d8d47bffaab07a92577993",\n "45e6663c65172e225e2531df3dce58096ed6e9a7d0fd7819e5b6f094a41731a0",\n "f2af064d46f1db1d96c7c9508a462993851e42f29566f2101ea3a1c51e5e451c",\n "b75c046d06f86eea41826999211ab5e6c9cb5fe067ade561fb5dc5f0b52d4584",\n "1fb9fbb26b78c2e9c56abf8e39e4cb278a5a382d53115dcb1624fdefca762865",\n "d78c4d12f6d50278be6320df1fe10beeef8723558cdb12d9d6c7d1aa8180498b",\n "c8fa7d3a31906893c47df234318e94bc4371b55ac54edc60b2c09afd8a9291c6",\n "5236dc9c55f19d8aed50078cc6ecd1de85041afa65003276fc311c14d5a74d0a",\n "efb9d548ff94246a22cfa8e06b70689d8f3edf69c8ad45c3811e0d340b4b10ff",\n "842c8d78d995f49b569934cf5e8316ba1d93a1d73e757210d5f0eb7e1ed52049",\n "b95dcfba35d31ab263bfab939280c71893bdb39e3a744c2f3cc38612ebcbb42a",\n "d6387171a203c64fd1c09514a028cf813d2ffccf968831c92cdf22287992e004",\n "b8d098f358ce5e8fa2900ac18435078652353a32a19ef2fd038bf82eee3a0731"\n ],\n "detailsMarkdown": "### Attack Progression\\n- **Initial Access**: The attack began with a spearphishing attachment delivered via Microsoft Office documents. The documents contained malicious macros that executed upon opening.\\n- **Execution**: The malicious macros executed various commands, including the use of `certutil` to decode and execute payloads, and `regsvr32` to register malicious DLLs.\\n- **Persistence**: The attackers established persistence by modifying registry run keys and creating scheduled tasks.\\n- **Credential Access**: The attackers attempted to capture credentials using `osascript` on macOS systems.\\n- **Defense Evasion**: The attackers used code signing with invalid or expired certificates to evade detection.\\n- **Command and Control**: The attackers established command and control channels using various techniques, including the use of `mshta` and `powershell` scripts.\\n- **Exfiltration**: The attackers exfiltrated data using tools like `curl` to transfer data to remote servers.\\n- **Impact**: The attackers deployed ransomware, including `Sodinokibi` and `Bumblebee`, to encrypt files and demand ransom payments.\\n\\n### Affected Hosts and Users\\n- **Hosts**: Multiple hosts across different operating systems (Windows, macOS, Linux) were affected.\\n- **Users**: The attacks targeted various users, including administrators and regular users.\\n\\n### Known Threat Groups\\n- The attack patterns and techniques used in this campaign are consistent with those employed by known threat groups such as `Emotet`, `Qbot`, and `Sodinokibi`.\\n\\n### Recommendations\\n- **Immediate Actions**: Isolate affected systems, reset passwords, and review network traffic for signs of command and control communications.\\n- **Long-term Actions**: Implement multi-factor authentication, conduct regular security awareness training, and deploy advanced endpoint protection solutions.",\n "entitySummaryMarkdown": "{{ host.name 9ed6a9db-da4d-4877-a2b4-f7a22cc55e9a }} {{ user.name c45d8d76-bff6-4c4b-aa5a-62eb15d68adb }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence",\n "Credential Access",\n "Defense Evasion",\n "Command and Control",\n "Exfiltration",\n "Impact"\n ],\n "summaryMarkdown": "A sophisticated multi-stage attack was detected, involving spearphishing, credential access, and ransomware deployment. The attack targeted multiple hosts and users across different operating systems.",\n "title": "Multi-Stage Cyber Attack Detected"\n }\n ]\n}\n```'; + + const expected = + '{\n "insights": [\n {\n "alertIds": [\n "a609473a23b3a66a40f2bba06795c28a0c12863c6931f39e472d069f5600cbae",\n "04a9ded2b4f10ea407711f0010d426ad328eea43ae53e1e0bf166c058947dff6",\n "8d53b9838181299b3c0b1544ea469216d72ad2234a1cce44017dd248a08d78d1",\n "51d0080ffcc1982dbae7c31a9a021f7b51422000dec1f0e0bb58bd61d934c893",\n "d93302956bee58d538f6f7a6cbf944e549e8466dacfb554a302dce46a069eef0",\n "75c89f679397f089716034cde20f5547a2e6bdd1606b1e002e0976ab339c4cd9",\n "5d8e9427c0ecc4daa5809bfe250b9a382c53e81e8f39eec87499d28efdda9300",\n "f18ac1874f510fd3fabb0ae48d0714f4952b294496ef1d993e3eb03f839e2d83",\n "e37cb31213c4c4e80beaf9f75e7966f88cdd86a228c6cb1a28e46356410fa78f",\n "cf70077b8888e8fbe434808fddbaf65d97fff244bb185a595cf0ad487e9c5850",\n "01bea609f0880b10b7b3c6cf6e8245ef0f134386fdcbf2a167e72487e0bda616",\n "289621edc88fd8b4775c541e46bcfdea40538291266179c59a5ca5afbee74cfc",\n "ba121c2045058b62a92e6a3abadd3c78a005b89129630e2271b2f45d5fd995b2",\n "fceb940b252be079df3629550d852bd2793f79071c917227268fa1b805abc8d1",\n "7044589c27bab148cdb97d9e2eeb88bd924fca82a6a05a53ec94dcadf8e56303",\n "1b68be35429f52280456aab17dd94191fe5c47fed9768f00d9f9e9044a08bbb5",\n "52478d4a119bbc44bec67f384f83dfa20b33cf9963177e619cd47a32bababe12",\n "fecbbb8924493b466e8f5744e0875a9ee91f326213b691b576b13da3fb875ebf",\n "c46bbdeb7b59f52c976e7e4f30e3d5c65f417d716cb140096b5edba52b1449a1",\n "f12caebcbda087fc8b49cdced64a8997dd1428f4cf91ebb251434a55126399b3",\n "c7478edbd13af443cfafc57d50e5206c5ae8c0f9c9cabc073fdc2d3946559617",\n "3585ae62651929ef405f9783410d7a94f4254d299205e22f22966178f189bb11",\n "f50f531912af1d31a66a0e37d4c0d9c571c2cca6bef2c7f8453eb3ab67c4d1a0",\n "95a9403f0bb97d03fc3c2eb06386503831766f541b736468088092c5e0e25830",\n "c1292c67f3ccd2cb2651c601f0816122cfa459276fa5fc89b40c62d1a793963e",\n "8911886e1b2964176f70eaee2aa6693ce101ee9c8ec5434acdc7ff18616ec31c",\n "bfbfb02c03c6f69fc2352c48d8fd7c7e4b557c611e16956fbb63e337a513e699",\n "064cbdc1932029fcb34f6ba685211b971afde3b8aa4325054bedaf4c9e4587ed",\n "9fd5d0ca9b9fff6e37f1114ad874103badb2b0570ef143cd4a26a553effdff00",\n "9e2687f26f04b5a8def3266f89fbe7217da2d4355c3b035268df1802f1342c81",\n "64557c4006c52119c01f6e3e582ce1b8207b2e8f64aaaa630ca1fd156c01ea1e",\n "df98d2568c986d101af055f78c7e2a39299627531c28012b5025d10e2ec1b208",\n "10683db11fb21cae36577f83722c686c2fc691d2be6fc4396f2733564f3210d1",\n "f46e7b3266200e3e23b15b5acea7bb934e2c17d23058e10daeed51f036f4932b",\n "3c77d55f912b80b66cc1e4e1df02a22ddee07c50338a409374fb2567d2fb4ca3",\n "8ec169c0fdf558c0d9d9ad8dedad0898b15bb718421b4cab8f5cce4ebcb78254",\n "4119a1705f993588f8d1d576e567ec17f102aeafe535e53bb56ec833418ccd08",\n "b53d06bfd23ab843dba67e2fde0da6364475b0bfb9c40cb8a6641cc4ecadec01",\n "1dcd85c8279fd7152dadecfc547cce06261d23ef4589fe4fdcc92b1ceeb76c0f",\n "d4ed490b1d39925ee612058655030bdb7cecda3e5893e1c40dbbac852b72fbc6",\n "2ecc96c4d51f5338684c08e7c67357e504abfec6fc4f21753a3c941189db68e1",\n "0c9fb123686bc739d117ee4f607ffbcef39f1f72e7eab6d01b70bbb40480b3d6",\n "162be5e04f54a5cd475d2437fe769ee044324b0a32ce83a735f61719b8b5fd63",\n "21eae60b4b29f7f01cc7006372374e1c5d6912858c33397cdbe4470df97fba79",\n "0409539590b6d9b80f7071d3d5658434f982ba7957aa6a5037f8b7a73b70100d",\n "5e8e654df34a9053f8b90e4ade25520dbee5994ebf7da531e1e7255d029ab031",\n "3ef381b2d29d71bc3ac8580d333344948a2664855a89ff037299a8b4aa663293",\n "0aef1fe2506842f9c53549049b47a8166bcc3d6efe2d8fcf1e57f3a634ed137c",\n "c2d12dacd0cd6ef4a7386c8d0146d3eb91a7e1e9f2d8d47bffaab07a92577993",\n "45e6663c65172e225e2531df3dce58096ed6e9a7d0fd7819e5b6f094a41731a0",\n "f2af064d46f1db1d96c7c9508a462993851e42f29566f2101ea3a1c51e5e451c",\n "b75c046d06f86eea41826999211ab5e6c9cb5fe067ade561fb5dc5f0b52d4584",\n "1fb9fbb26b78c2e9c56abf8e39e4cb278a5a382d53115dcb1624fdefca762865",\n "d78c4d12f6d50278be6320df1fe10beeef8723558cdb12d9d6c7d1aa8180498b",\n "c8fa7d3a31906893c47df234318e94bc4371b55ac54edc60b2c09afd8a9291c6",\n "5236dc9c55f19d8aed50078cc6ecd1de85041afa65003276fc311c14d5a74d0a",\n "efb9d548ff94246a22cfa8e06b70689d8f3edf69c8ad45c3811e0d340b4b10ff",\n "842c8d78d995f49b569934cf5e8316ba1d93a1d73e757210d5f0eb7e1ed52049",\n "b95dcfba35d31ab263bfab939280c71893bdb39e3a744c2f3cc38612ebcbb42a",\n "d6387171a203c64fd1c09514a028cf813d2ffccf968831c92cdf22287992e004",\n "b8d098f358ce5e8fa2900ac18435078652353a32a19ef2fd038bf82eee3a0731"\n ],\n "detailsMarkdown": "### Attack Progression\\n- **Initial Access**: The attack began with a spearphishing attachment delivered via Microsoft Office documents. The documents contained malicious macros that executed upon opening.\\n- **Execution**: The malicious macros executed various commands, including the use of `certutil` to decode and execute payloads, and `regsvr32` to register malicious DLLs.\\n- **Persistence**: The attackers established persistence by modifying registry run keys and creating scheduled tasks.\\n- **Credential Access**: The attackers attempted to capture credentials using `osascript` on macOS systems.\\n- **Defense Evasion**: The attackers used code signing with invalid or expired certificates to evade detection.\\n- **Command and Control**: The attackers established command and control channels using various techniques, including the use of `mshta` and `powershell` scripts.\\n- **Exfiltration**: The attackers exfiltrated data using tools like `curl` to transfer data to remote servers.\\n- **Impact**: The attackers deployed ransomware, including `Sodinokibi` and `Bumblebee`, to encrypt files and demand ransom payments.\\n\\n### Affected Hosts and Users\\n- **Hosts**: Multiple hosts across different operating systems (Windows, macOS, Linux) were affected.\\n- **Users**: The attacks targeted various users, including administrators and regular users.\\n\\n### Known Threat Groups\\n- The attack patterns and techniques used in this campaign are consistent with those employed by known threat groups such as `Emotet`, `Qbot`, and `Sodinokibi`.\\n\\n### Recommendations\\n- **Immediate Actions**: Isolate affected systems, reset passwords, and review network traffic for signs of command and control communications.\\n- **Long-term Actions**: Implement multi-factor authentication, conduct regular security awareness training, and deploy advanced endpoint protection solutions.",\n "entitySummaryMarkdown": "{{ host.name 9ed6a9db-da4d-4877-a2b4-f7a22cc55e9a }} {{ user.name c45d8d76-bff6-4c4b-aa5a-62eb15d68adb }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence",\n "Credential Access",\n "Defense Evasion",\n "Command and Control",\n "Exfiltration",\n "Impact"\n ],\n "summaryMarkdown": "A sophisticated multi-stage attack was detected, involving spearphishing, credential access, and ransomware deployment. The attack targeted multiple hosts and users across different operating systems.",\n "title": "Multi-Stage Cyber Attack Detected"\n }\n ]\n}'; + + expect(extractJson(input)).toBe(expected); + }); + + it('handles "Here is my analysis of the security events in JSON format" (real world edge case)', () => { + const input = + 'Here is my analysis of the security events in JSON format:\n\n```json\n{\n "insights": [\n {\n "alertIds": [\n "d776c8406fd81427b1f166550ac1b949017da7a13dc734594e4b05f24622b26e",\n "504c012054cfe91986311b4e6bc8523914434fab590e5c07c0328fab6566753c",\n "b706b8c19e68cc4f54b69f0a93e32b10f4102b610213b7826fb1d303b90a0536",\n "7763ebe716c47f64987362a9fb120d73873c77d26ad915f2c3d57c5dd3b7eed0",\n "25c61e0423a9bfd7f268ca6e9b67d4f507207c0cb1e1b4701aa5248cb3866f1f",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:17.566Z }}, a malicious file with SHA256 hash {{ file.hash.sha256 74ef6cc38f5a1a80148752b63c117e6846984debd2af806c65887195a8eccc56 }} was detected on {{ host.name SRVNIX05 }}\\n- The file was initially downloaded as a zip archive and extracted to /home/ubuntu/\\n- The malware, identified as Linux.Trojan.BPFDoor, was then copied to /dev/shm/kdmtmpflush and executed\\n- This trojan allows remote attackers to gain backdoor access to the compromised Linux system\\n- The malware was executed with root privileges, indicating a serious compromise\\n- Network connections and other malicious activities from this backdoor should be investigated",\n "entitySummaryMarkdown": "{{ host.name SRVNIX05 }} compromised by Linux.Trojan.BPFDoor malware executed as {{ user.name root }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Linux.Trojan.BPFDoor malware detected and executed on {{ host.name SRVNIX05 }} with root privileges, allowing remote backdoor access",\n "title": "Linux Trojan BPFDoor Backdoor Detected"\n },\n {\n "alertIds": [\n "5946b409f49b0983de53e575db0874ef11b0544766f816dc702941a69a9b0dd1",\n "aa0ba23872c48a8ee761591c5bb0a9ed8258c51b27111cc72dbe8624a0b7da96",\n "b60a5c344b579cab9406becdec14a11d56f4eccc2bf6caaf6eb72ddf1707124c",\n "4920ca19a22968e4ab0cf299974234699d9cce15545c401a2b8fd09d71f6e106",\n "26302b2afbe58c8dcfde950c7164262c626af0b85f0808f3d8632b1d6a406d16",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "41564c953dd101b942537110d175d2b269959c24dbf5b7c482e32851ab6f5dc1",\n "12e102970920f5f938b21effb09394c00540075fc4057ec79e221046a8b6ba0f"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:33.570Z }}, suspicious activity was detected on {{ host.name SRVMAC08 }}\\n- A malicious application \\"My Go Application.app\\" was executed, likely masquerading as a legitimate program\\n- The malware attempted to access the user\'s keychain to steal credentials\\n- It executed a file named {{ file.name unix1 }} which tried to access {{ file.path /Users/james/library/Keychains/login.keychain-db }}\\n- The malware also attempted to display a fake system preferences dialog to phish the user\'s password\\n- This attack targeted {{ user.name james }}, who has high user criticality",\n "entitySummaryMarkdown": "{{ host.name SRVMAC08 }} infected with malware targeting {{ user.name james }}\'s credentials",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Credential Access" \n ],\n "summaryMarkdown": "Malware on {{ host.name SRVMAC08 }} attempted to steal keychain credentials and phish password from {{ user.name james }}",\n "title": "macOS Credential Theft Attempt"\n },\n {\n "alertIds": [\n "a492cd3202717d0c86f9b44623b12ac4d19855722e0fadb2f84a547afb45871a",\n "7fdf3a399b0a6df74784f478c2712a0e47ff997f73701593b3a5a56fa452056f",\n "bf33e5f004b6f6f41e362f929b3fa16b5ea9ecbb0f6389acd17dfcfb67ff3ae9",\n "b6559664247c438f9cd15022feb87855253c3cef882cc52d2e064f2693977f1c",\n "636a5a24b810bf2dbc5e2417858ac218b1fadb598fa55676745f88c0509f3e48",\n "fc0f6f9939277cc4f526148c15813f5d48094e557fdcf0ba9e773b2a16ec8c2e",\n "0029a93e8f72dce05a22ca0cc5a5cd1ca8a29b93b3c8864f7623f10b98d79084",\n "67f41b973f82fc141d75fbbd1d6caba11066c19b2a1c720fcec9e681e1cfa60c",\n "79774ae772225e94b6183f5ea394572ebe24452be99100bab145173c57c73d3b"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:54.836Z }}, malicious activity was detected on {{ host.name SRVWIN01 }}\\n- An Excel file was used to drop and execute malware\\n- The malware used certutil.exe to decode a malicious payload\\n- A suspicious executable {{ file.name Q3C7N1V8.exe }} was created in C:\\\\ProgramData\\\\\\n- The malware established persistence by modifying registry run keys\\n- It then executed a DLL {{ file.name cdnver.dll }} using rundll32.exe\\n- This attack chain indicates a sophisticated malware infection, likely part of an ongoing attack campaign",\n "entitySummaryMarkdown": "{{ host.name SRVWIN01 }} infected via malicious Excel file executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access", \n "Execution",\n "Persistence",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN01 }} via malicious Excel file, establishing persistence and executing additional payloads",\n "title": "Excel-based Malware Infection Chain"\n },\n {\n "alertIds": [\n "801ec41afa5f05a7cafefe4eaff87be1f9eb7ecbfcfc501bd83a12f19e742be0",\n "eafd7577e1d88b2c4fc3d0e3eb54b2a315f79996f075ba3c57d6f2ae7181c53b",\n "eb8fee0ceacc8caec4757e95ec132a42bae4ba7841126ce9616873e01e806ddf",\n "69dcd5e48424cc8a04a965f5bec7539c8221ac556a7b93c531cdc7e02b58c191",\n "6c81da91ad4ec313c5a4aa970e1fdf7c3ee6dbfa8536c734bd12c72f1abe3a09",\n "584d904ea196623eb794df40565797656e24d05a707638447b5e53c05d520510",\n "46d05beb516dae1ad2f168084cdeb5bfd35ac1b1194bd65aa1c837fb3b77c21d",\n "c79fe367d985d9a5d9ee723ce94977b88fe1bbb3ec8e2ffbb7b3ee134d6b49ef",\n "3ef6baa7c7c99cad5b7832e6a778a7d1ea2d88729a3e50fbf2b821d0e57f2740",\n "1fbe36af64b587d7604812f6a248754cfe8c1d80b0551046c1fc95640d0ba538",\n "4451f6a45edc2d90f85717925071457e88dd41d0ee3d3c377f5721a254651513",\n "7ec9f53a2c4571325476ad2f4de3d2ecb49609b35a4a30a33d8d57e815d09f52",\n "ca57fd3a83e06419ce8299eefd3c783bd3d33b46ce47ffd27e2abdcb2b3e0955"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:14.847Z }}, a malicious OneNote file was opened on {{ host.name SRVWIN04 }}\\n- The OneNote file executed an embedded HTA file using mshta.exe\\n- The HTA file then downloaded additional malware using curl.exe\\n- A suspicious DLL {{ file.path C:\\\\ProgramData\\\\121.png }} was loaded using rundll32.exe\\n- The malware injected shellcode into legitimate Windows processes like AtBroker.exe\\n- Memory scans detected signatures matching the Qbot banking trojan\\n- The malware established persistence by modifying registry run keys\\n- It also performed domain trust enumeration, indicating potential lateral movement preparation\\n- This sophisticated attack chain suggests a targeted intrusion by an advanced threat actor",\n "entitySummaryMarkdown": "{{ host.name SRVWIN04 }} compromised via malicious OneNote file opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution", \n "Persistence",\n "Defense Evasion",\n "Discovery"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN04 }} via OneNote file, downloading Qbot trojan and preparing for potential lateral movement",\n "title": "OneNote-based Qbot Infection Chain"\n },\n {\n "alertIds": [\n "7150ee5a9571c6028573bf7d9c2ed0da15c3387ee3c8f668741799496f7b4ae9",\n "6053ca3481a9307d3a8626fe055357541bb53d97f5deb1b7b346ec86441c335b",\n "d9c3908a4ac46b90270e6aab8217ab6385a114574931026f1df8cfc930260ff6",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070",\n "f045dc2a57582944b6e198e685e98bf02f86b5eb23ddbbdbb015c8568867122c",\n "171fe0490d48e9cac6f5b46aec7bfa67f3ecb96af308027018ca881bae1ce5d7",\n "0e22ea9514fd663a3841a212b19736fd1579c301d80f4838f25adeec24de4cf6",\n "9d8fdb59213e5a950d93253f9f986c730c877a70493c4f47ad0de52ef50c42f1"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:58.609Z }}, a malicious executable was run on {{ host.name SRVWIN02 }}\\n- The malware injected shellcode into the legitimate MsMpEng.exe (Windows Defender) process\\n- Memory scans detected signatures matching the Sodinokibi (REvil) ransomware\\n- The malware created ransom notes and began encrypting files\\n- It also attempted to enable network discovery, likely to spread to other systems\\n- This indicates an active ransomware infection that could quickly spread across the network",\n "entitySummaryMarkdown": "{{ host.name SRVWIN02 }} infected with Sodinokibi ransomware executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion",\n "Impact"\n ],\n "summaryMarkdown": "Sodinokibi (REvil) ransomware detected on {{ host.name SRVWIN02 }}, actively encrypting files and attempting to spread",\n "title": "Active Sodinokibi Ransomware Infection"\n },\n {\n "alertIds": [\n "6f8e71d59956c6dbed5c88986cdafd4386684e3879085b2742e1f2d38b282066",\n "c13b78fbfef05ddc81c73b436ccb5288d8cd52a46175638b1b3b0d311f8b53e8",\n "b0f3d3f5bfc0b1d1f3c7e219ee44dc225fa26cafd40697073a636b44cf6054ad"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:22.077Z }}, suspicious activity was detected on {{ host.name SRVWIN06 }}\\n- The msiexec.exe process spawned an unusual PowerShell child process\\n- The PowerShell process executed a script from a suspicious temporary directory\\n- Memory scans of the PowerShell process detected signatures matching the Bumblebee malware loader\\n- Bumblebee is known to be used by multiple ransomware groups as an initial access vector\\n- This indicates a likely ongoing attack attempting to deploy additional malware or ransomware",\n "entitySummaryMarkdown": "{{ host.name SRVWIN06 }} infected with Bumblebee malware loader via {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Bumblebee malware loader detected on {{ host.name SRVWIN06 }}, likely attempting to deploy additional payloads",\n "title": "Bumblebee Malware Loader Detected"\n },\n {\n "alertIds": [\n "f629babc51c3628517d8a7e1f0662124ee41e4328b1dbcf72dc3fc6f2e410d33",\n "627d00600f803366edb83700b546a4bf486e2990ac7140d842e898eb6e298e83",\n "6181847506974ed4458f03b60919c4a306197b5cb040ab324d2d1f6d0ca5bde1",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "df26b2d23068b77fdc001ea44f46505a259f02ceccc9fa0b2401c5e35190e710",\n "9c038ff779bd0ff514a1ff2b55caa359189d8bcebc48c6ac14a789946e87eaed"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:27.839Z }}, a malicious Word document was opened on {{ host.name SRVWIN07 }}\\n- The document spawned wscript.exe to execute a malicious VBS script\\n- The VBS script then launched a PowerShell process with suspicious arguments\\n- PowerShell was used to create a scheduled task for persistence\\n- This attack chain indicates a likely attempt to establish a foothold for further malicious activities",\n "entitySummaryMarkdown": "{{ host.name SRVWIN07 }} compromised via malicious Word document opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Malicious Word document on {{ host.name SRVWIN07 }} led to execution of VBS and PowerShell scripts, establishing persistence via scheduled task",\n "title": "Malicious Document Leads to Persistence"\n }\n ]\n}'; + + const expected = + '{\n "insights": [\n {\n "alertIds": [\n "d776c8406fd81427b1f166550ac1b949017da7a13dc734594e4b05f24622b26e",\n "504c012054cfe91986311b4e6bc8523914434fab590e5c07c0328fab6566753c",\n "b706b8c19e68cc4f54b69f0a93e32b10f4102b610213b7826fb1d303b90a0536",\n "7763ebe716c47f64987362a9fb120d73873c77d26ad915f2c3d57c5dd3b7eed0",\n "25c61e0423a9bfd7f268ca6e9b67d4f507207c0cb1e1b4701aa5248cb3866f1f",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:17.566Z }}, a malicious file with SHA256 hash {{ file.hash.sha256 74ef6cc38f5a1a80148752b63c117e6846984debd2af806c65887195a8eccc56 }} was detected on {{ host.name SRVNIX05 }}\\n- The file was initially downloaded as a zip archive and extracted to /home/ubuntu/\\n- The malware, identified as Linux.Trojan.BPFDoor, was then copied to /dev/shm/kdmtmpflush and executed\\n- This trojan allows remote attackers to gain backdoor access to the compromised Linux system\\n- The malware was executed with root privileges, indicating a serious compromise\\n- Network connections and other malicious activities from this backdoor should be investigated",\n "entitySummaryMarkdown": "{{ host.name SRVNIX05 }} compromised by Linux.Trojan.BPFDoor malware executed as {{ user.name root }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Linux.Trojan.BPFDoor malware detected and executed on {{ host.name SRVNIX05 }} with root privileges, allowing remote backdoor access",\n "title": "Linux Trojan BPFDoor Backdoor Detected"\n },\n {\n "alertIds": [\n "5946b409f49b0983de53e575db0874ef11b0544766f816dc702941a69a9b0dd1",\n "aa0ba23872c48a8ee761591c5bb0a9ed8258c51b27111cc72dbe8624a0b7da96",\n "b60a5c344b579cab9406becdec14a11d56f4eccc2bf6caaf6eb72ddf1707124c",\n "4920ca19a22968e4ab0cf299974234699d9cce15545c401a2b8fd09d71f6e106",\n "26302b2afbe58c8dcfde950c7164262c626af0b85f0808f3d8632b1d6a406d16",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "41564c953dd101b942537110d175d2b269959c24dbf5b7c482e32851ab6f5dc1",\n "12e102970920f5f938b21effb09394c00540075fc4057ec79e221046a8b6ba0f"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:33.570Z }}, suspicious activity was detected on {{ host.name SRVMAC08 }}\\n- A malicious application \\"My Go Application.app\\" was executed, likely masquerading as a legitimate program\\n- The malware attempted to access the user\'s keychain to steal credentials\\n- It executed a file named {{ file.name unix1 }} which tried to access {{ file.path /Users/james/library/Keychains/login.keychain-db }}\\n- The malware also attempted to display a fake system preferences dialog to phish the user\'s password\\n- This attack targeted {{ user.name james }}, who has high user criticality",\n "entitySummaryMarkdown": "{{ host.name SRVMAC08 }} infected with malware targeting {{ user.name james }}\'s credentials",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Credential Access" \n ],\n "summaryMarkdown": "Malware on {{ host.name SRVMAC08 }} attempted to steal keychain credentials and phish password from {{ user.name james }}",\n "title": "macOS Credential Theft Attempt"\n },\n {\n "alertIds": [\n "a492cd3202717d0c86f9b44623b12ac4d19855722e0fadb2f84a547afb45871a",\n "7fdf3a399b0a6df74784f478c2712a0e47ff997f73701593b3a5a56fa452056f",\n "bf33e5f004b6f6f41e362f929b3fa16b5ea9ecbb0f6389acd17dfcfb67ff3ae9",\n "b6559664247c438f9cd15022feb87855253c3cef882cc52d2e064f2693977f1c",\n "636a5a24b810bf2dbc5e2417858ac218b1fadb598fa55676745f88c0509f3e48",\n "fc0f6f9939277cc4f526148c15813f5d48094e557fdcf0ba9e773b2a16ec8c2e",\n "0029a93e8f72dce05a22ca0cc5a5cd1ca8a29b93b3c8864f7623f10b98d79084",\n "67f41b973f82fc141d75fbbd1d6caba11066c19b2a1c720fcec9e681e1cfa60c",\n "79774ae772225e94b6183f5ea394572ebe24452be99100bab145173c57c73d3b"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:54.836Z }}, malicious activity was detected on {{ host.name SRVWIN01 }}\\n- An Excel file was used to drop and execute malware\\n- The malware used certutil.exe to decode a malicious payload\\n- A suspicious executable {{ file.name Q3C7N1V8.exe }} was created in C:\\\\ProgramData\\\\\\n- The malware established persistence by modifying registry run keys\\n- It then executed a DLL {{ file.name cdnver.dll }} using rundll32.exe\\n- This attack chain indicates a sophisticated malware infection, likely part of an ongoing attack campaign",\n "entitySummaryMarkdown": "{{ host.name SRVWIN01 }} infected via malicious Excel file executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access", \n "Execution",\n "Persistence",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN01 }} via malicious Excel file, establishing persistence and executing additional payloads",\n "title": "Excel-based Malware Infection Chain"\n },\n {\n "alertIds": [\n "801ec41afa5f05a7cafefe4eaff87be1f9eb7ecbfcfc501bd83a12f19e742be0",\n "eafd7577e1d88b2c4fc3d0e3eb54b2a315f79996f075ba3c57d6f2ae7181c53b",\n "eb8fee0ceacc8caec4757e95ec132a42bae4ba7841126ce9616873e01e806ddf",\n "69dcd5e48424cc8a04a965f5bec7539c8221ac556a7b93c531cdc7e02b58c191",\n "6c81da91ad4ec313c5a4aa970e1fdf7c3ee6dbfa8536c734bd12c72f1abe3a09",\n "584d904ea196623eb794df40565797656e24d05a707638447b5e53c05d520510",\n "46d05beb516dae1ad2f168084cdeb5bfd35ac1b1194bd65aa1c837fb3b77c21d",\n "c79fe367d985d9a5d9ee723ce94977b88fe1bbb3ec8e2ffbb7b3ee134d6b49ef",\n "3ef6baa7c7c99cad5b7832e6a778a7d1ea2d88729a3e50fbf2b821d0e57f2740",\n "1fbe36af64b587d7604812f6a248754cfe8c1d80b0551046c1fc95640d0ba538",\n "4451f6a45edc2d90f85717925071457e88dd41d0ee3d3c377f5721a254651513",\n "7ec9f53a2c4571325476ad2f4de3d2ecb49609b35a4a30a33d8d57e815d09f52",\n "ca57fd3a83e06419ce8299eefd3c783bd3d33b46ce47ffd27e2abdcb2b3e0955"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:14.847Z }}, a malicious OneNote file was opened on {{ host.name SRVWIN04 }}\\n- The OneNote file executed an embedded HTA file using mshta.exe\\n- The HTA file then downloaded additional malware using curl.exe\\n- A suspicious DLL {{ file.path C:\\\\ProgramData\\\\121.png }} was loaded using rundll32.exe\\n- The malware injected shellcode into legitimate Windows processes like AtBroker.exe\\n- Memory scans detected signatures matching the Qbot banking trojan\\n- The malware established persistence by modifying registry run keys\\n- It also performed domain trust enumeration, indicating potential lateral movement preparation\\n- This sophisticated attack chain suggests a targeted intrusion by an advanced threat actor",\n "entitySummaryMarkdown": "{{ host.name SRVWIN04 }} compromised via malicious OneNote file opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution", \n "Persistence",\n "Defense Evasion",\n "Discovery"\n ],\n "summaryMarkdown": "Sophisticated malware infection on {{ host.name SRVWIN04 }} via OneNote file, downloading Qbot trojan and preparing for potential lateral movement",\n "title": "OneNote-based Qbot Infection Chain"\n },\n {\n "alertIds": [\n "7150ee5a9571c6028573bf7d9c2ed0da15c3387ee3c8f668741799496f7b4ae9",\n "6053ca3481a9307d3a8626fe055357541bb53d97f5deb1b7b346ec86441c335b",\n "d9c3908a4ac46b90270e6aab8217ab6385a114574931026f1df8cfc930260ff6",\n "ea99e1633177f0c82e5126d4c999db2128c3adac6af4c7f4f183abc44486f070",\n "f045dc2a57582944b6e198e685e98bf02f86b5eb23ddbbdbb015c8568867122c",\n "171fe0490d48e9cac6f5b46aec7bfa67f3ecb96af308027018ca881bae1ce5d7",\n "0e22ea9514fd663a3841a212b19736fd1579c301d80f4838f25adeec24de4cf6",\n "9d8fdb59213e5a950d93253f9f986c730c877a70493c4f47ad0de52ef50c42f1"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:49:58.609Z }}, a malicious executable was run on {{ host.name SRVWIN02 }}\\n- The malware injected shellcode into the legitimate MsMpEng.exe (Windows Defender) process\\n- Memory scans detected signatures matching the Sodinokibi (REvil) ransomware\\n- The malware created ransom notes and began encrypting files\\n- It also attempted to enable network discovery, likely to spread to other systems\\n- This indicates an active ransomware infection that could quickly spread across the network",\n "entitySummaryMarkdown": "{{ host.name SRVWIN02 }} infected with Sodinokibi ransomware executed by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion",\n "Impact"\n ],\n "summaryMarkdown": "Sodinokibi (REvil) ransomware detected on {{ host.name SRVWIN02 }}, actively encrypting files and attempting to spread",\n "title": "Active Sodinokibi Ransomware Infection"\n },\n {\n "alertIds": [\n "6f8e71d59956c6dbed5c88986cdafd4386684e3879085b2742e1f2d38b282066",\n "c13b78fbfef05ddc81c73b436ccb5288d8cd52a46175638b1b3b0d311f8b53e8",\n "b0f3d3f5bfc0b1d1f3c7e219ee44dc225fa26cafd40697073a636b44cf6054ad"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:22.077Z }}, suspicious activity was detected on {{ host.name SRVWIN06 }}\\n- The msiexec.exe process spawned an unusual PowerShell child process\\n- The PowerShell process executed a script from a suspicious temporary directory\\n- Memory scans of the PowerShell process detected signatures matching the Bumblebee malware loader\\n- Bumblebee is known to be used by multiple ransomware groups as an initial access vector\\n- This indicates a likely ongoing attack attempting to deploy additional malware or ransomware",\n "entitySummaryMarkdown": "{{ host.name SRVWIN06 }} infected with Bumblebee malware loader via {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Execution",\n "Defense Evasion"\n ],\n "summaryMarkdown": "Bumblebee malware loader detected on {{ host.name SRVWIN06 }}, likely attempting to deploy additional payloads",\n "title": "Bumblebee Malware Loader Detected"\n },\n {\n "alertIds": [\n "f629babc51c3628517d8a7e1f0662124ee41e4328b1dbcf72dc3fc6f2e410d33",\n "627d00600f803366edb83700b546a4bf486e2990ac7140d842e898eb6e298e83",\n "6181847506974ed4458f03b60919c4a306197b5cb040ab324d2d1f6d0ca5bde1",\n "3aba59cd449be763e5b50ab954e39936ab3035be36010810e340e277b5670017",\n "df26b2d23068b77fdc001ea44f46505a259f02ceccc9fa0b2401c5e35190e710",\n "9c038ff779bd0ff514a1ff2b55caa359189d8bcebc48c6ac14a789946e87eaed"\n ],\n "detailsMarkdown": "- At {{ kibana.alert.original_time 2024-05-16T18:50:27.839Z }}, a malicious Word document was opened on {{ host.name SRVWIN07 }}\\n- The document spawned wscript.exe to execute a malicious VBS script\\n- The VBS script then launched a PowerShell process with suspicious arguments\\n- PowerShell was used to create a scheduled task for persistence\\n- This attack chain indicates a likely attempt to establish a foothold for further malicious activities",\n "entitySummaryMarkdown": "{{ host.name SRVWIN07 }} compromised via malicious Word document opened by {{ user.name Administrator }}",\n "mitreAttackTactics": [\n "Initial Access",\n "Execution",\n "Persistence"\n ],\n "summaryMarkdown": "Malicious Word document on {{ host.name SRVWIN07 }} led to execution of VBS and PowerShell scripts, establishing persistence via scheduled task",\n "title": "Malicious Document Leads to Persistence"\n }\n ]\n}'; + + expect(extractJson(input)).toBe(expected); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts new file mode 100644 index 0000000000000..79d3f9c0d0599 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/extract_json/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const extractJson = (input: string): string => { + const regex = /```json\s*([\s\S]*?)(?:\s*```|$)/; + const match = input.match(regex); + + if (match && match[1]) { + return match[1].trim(); + } + + return input; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx new file mode 100644 index 0000000000000..7d6db4dd72dfd --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.test.tsx @@ -0,0 +1,90 @@ +/* + * 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 { generationsAreRepeating } from '.'; + +describe('getIsGenerationRepeating', () => { + it('returns true when all previous generations are the same as the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen1', 'gen1'], // <-- all the same, length 3 + sampleLastNGenerations: 3, + }); + + expect(result).toBe(true); + }); + + it('returns false when some of the previous generations are NOT the same as the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen2', 'gen1'], // <-- some are different, length 3 + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns true when all *sampled* generations are the same as the current generation, and there are older samples past the last N', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: [ + 'gen2', // <-- older sample will be ignored + 'gen1', + 'gen1', + 'gen1', + ], + sampleLastNGenerations: 3, + }); + + expect(result).toBe(true); + }); + + it('returns false when some of the *sampled* generations are NOT the same as the current generation, and there are additional samples past the last N', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: [ + 'gen1', // <-- older sample will be ignored + 'gen1', + 'gen1', + 'gen2', + ], + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns false when sampling fewer generations than sampleLastNGenerations, and all are the same as the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen1'], // <-- same, but only 2 generations + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns false when sampling fewer generations than sampleLastNGenerations, and some are different from the current generation', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: ['gen1', 'gen2'], // <-- different, but only 2 generations + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); + + it('returns false when there are no previous generations to sample', () => { + const result = generationsAreRepeating({ + currentGeneration: 'gen1', + previousGenerations: [], + sampleLastNGenerations: 3, + }); + + expect(result).toBe(false); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx new file mode 100644 index 0000000000000..6cc9cd86c9d2f --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/generations_are_repeating/index.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** Returns true if the last n generations are repeating the same output */ +export const generationsAreRepeating = ({ + currentGeneration, + previousGenerations, + sampleLastNGenerations, +}: { + currentGeneration: string; + previousGenerations: string[]; + sampleLastNGenerations: number; +}): boolean => { + const generationsToSample = previousGenerations.slice(-sampleLastNGenerations); + + if (generationsToSample.length < sampleLastNGenerations) { + return false; // Not enough generations to sample + } + + return generationsToSample.every((generation) => generation === currentGeneration); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts new file mode 100644 index 0000000000000..7eacaad1d7e39 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_chain_with_format_instructions/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { Runnable } from '@langchain/core/runnables'; + +import { getOutputParser } from '../get_output_parser'; + +interface GetChainWithFormatInstructions { + chain: Runnable; + formatInstructions: string; + llmType: string; +} + +export const getChainWithFormatInstructions = ( + llm: ActionsClientLlm +): GetChainWithFormatInstructions => { + const outputParser = getOutputParser(); + const formatInstructions = outputParser.getFormatInstructions(); + + const prompt = ChatPromptTemplate.fromTemplate( + `Answer the user's question as best you can:\n{format_instructions}\n{query}` + ); + + const chain = prompt.pipe(llm); + const llmType = llm._llmType(); + + return { chain, formatInstructions, llmType }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts new file mode 100644 index 0000000000000..10b5c323891a1 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getCombined = ({ + combinedGenerations, + partialResponse, +}: { + combinedGenerations: string; + partialResponse: string; +}): string => `${combinedGenerations}${partialResponse}`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts new file mode 100644 index 0000000000000..4c9ac71f8310c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_combined_attack_discovery_prompt/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash/fp'; + +import { getAlertsContextPrompt } from '../../generate/helpers/get_alerts_context_prompt'; +import { getContinuePrompt } from '../get_continue_prompt'; + +/** + * Returns the the initial query, or the initial query combined with a + * continuation prompt and partial results + */ +export const getCombinedAttackDiscoveryPrompt = ({ + anonymizedAlerts, + attackDiscoveryPrompt, + combinedMaybePartialResults, +}: { + anonymizedAlerts: string[]; + attackDiscoveryPrompt: string; + /** combined results that may contain incomplete JSON */ + combinedMaybePartialResults: string; +}): string => { + const alertsContextPrompt = getAlertsContextPrompt({ + anonymizedAlerts, + attackDiscoveryPrompt, + }); + + return isEmpty(combinedMaybePartialResults) + ? alertsContextPrompt // no partial results yet + : `${alertsContextPrompt} + +${getContinuePrompt()} + +""" +${combinedMaybePartialResults} +""" + +`; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts new file mode 100644 index 0000000000000..628ba0531332c --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_continue_prompt/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getContinuePrompt = + (): string => `Continue exactly where you left off in the JSON output below, generating only the additional JSON output when it's required to complete your work. The additional JSON output MUST ALWAYS follow these rules: +1) it MUST conform to the schema above, because it will be checked against the JSON schema +2) it MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds), because it will be parsed as JSON +3) it MUST NOT repeat any the previous output, because that would prevent partial results from being combined +4) it MUST NOT restart from the beginning, because that would prevent partial results from being combined +5) it MUST NOT be prefixed or suffixed with additional text outside of the JSON, because that would prevent it from being combined and parsed as JSON: +`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts new file mode 100644 index 0000000000000..25bace13d40c8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_default_attack_discovery_prompt/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultAttackDiscoveryPrompt = (): string => + "You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. You MUST escape all JSON special characters (i.e. backslashes, double quotes, newlines, tabs, carriage returns, backspaces, and form feeds)."; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts new file mode 100644 index 0000000000000..569c8cf4e04a5 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { getOutputParser } from '.'; + +describe('getOutputParser', () => { + it('returns a structured output parser with the expected format instructions', () => { + const outputParser = getOutputParser(); + + const expected = `You must format your output as a JSON value that adheres to a given \"JSON Schema\" instance. + +\"JSON Schema\" is a declarative language that allows you to annotate and validate JSON documents. + +For example, the example \"JSON Schema\" instance {{\"properties\": {{\"foo\": {{\"description\": \"a list of test words\", \"type\": \"array\", \"items\": {{\"type\": \"string\"}}}}}}, \"required\": [\"foo\"]}}}} +would match an object with one required property, \"foo\". The \"type\" property specifies \"foo\" must be an \"array\", and the \"description\" property semantically describes it as \"a list of test words\". The items within \"foo\" must be strings. +Thus, the object {{\"foo\": [\"bar\", \"baz\"]}} is a well-formatted instance of this example \"JSON Schema\". The object {{\"properties\": {{\"foo\": [\"bar\", \"baz\"]}}}} is not well-formatted. + +Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas! + +Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock: +\`\`\`json +{"type":"object","properties":{"insights":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"alertIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The alert IDs that the insight is based on.\"},\"detailsMarkdown\":{\"type\":\"string\",\"description\":\"A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"},\"entitySummaryMarkdown\":{\"type\":\"string\",\"description\":\"A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"mitreAttackTactics\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"An array of MITRE ATT&CK tactic for the insight, using one of the following values: Reconnaissance,Initial Access,Execution,Persistence,Privilege Escalation,Discovery,Lateral Movement,Command and Control,Exfiltration\"},\"summaryMarkdown\":{\"type\":\"string\",\"description\":\"A markdown summary of insight, using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"title\":{\"type\":\"string\",\"description\":\"A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.\"}},\"required\":[\"alertIds\",\"detailsMarkdown\",\"summaryMarkdown\",\"title\"],\"additionalProperties\":false},\"description\":\"Insights with markdown that always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"}},\"required\":[\"insights\"],\"additionalProperties":false,\"$schema\":\"http://json-schema.org/draft-07/schema#\"} +\`\`\` +`; + + expect(outputParser.getFormatInstructions()).toEqual(expected); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts new file mode 100644 index 0000000000000..2ca0d72b63eb4 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/get_output_parser/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { StructuredOutputParser } from 'langchain/output_parsers'; + +import { AttackDiscoveriesGenerationSchema } from '../../generate/schema'; + +export const getOutputParser = () => + StructuredOutputParser.fromZodSchema(AttackDiscoveriesGenerationSchema); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts new file mode 100644 index 0000000000000..3f7a0a9d802b3 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/parse_combined_or_throw/index.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { AttackDiscovery } from '@kbn/elastic-assistant-common'; + +import { addTrailingBackticksIfNecessary } from '../add_trailing_backticks_if_necessary'; +import { extractJson } from '../extract_json'; +import { AttackDiscoveriesGenerationSchema } from '../../generate/schema'; + +export const parseCombinedOrThrow = ({ + combinedResponse, + generationAttempts, + llmType, + logger, + nodeName, +}: { + /** combined responses that maybe valid JSON */ + combinedResponse: string; + generationAttempts: number; + nodeName: string; + llmType: string; + logger?: Logger; +}): AttackDiscovery[] => { + const timestamp = new Date().toISOString(); + + const extractedJson = extractJson(addTrailingBackticksIfNecessary(combinedResponse)); + + logger?.debug( + () => + `${nodeName} node is parsing extractedJson (${llmType}) from attempt ${generationAttempts}` + ); + + const unvalidatedParsed = JSON.parse(extractedJson); + + logger?.debug( + () => + `${nodeName} node is validating combined response (${llmType}) from attempt ${generationAttempts}` + ); + + const validatedResponse = AttackDiscoveriesGenerationSchema.parse(unvalidatedParsed); + + logger?.debug( + () => + `${nodeName} node successfully validated Attack discoveries response (${llmType}) from attempt ${generationAttempts}` + ); + + return [...validatedResponse.insights.map((insight) => ({ ...insight, timestamp }))]; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts new file mode 100644 index 0000000000000..f938f6436db98 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/helpers/response_is_hallucinated/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const responseIsHallucinated = (result: string): boolean => + result.includes('{{ host.name hostNameValue }}'); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts new file mode 100644 index 0000000000000..e642e598e73f0 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/discard_previous_refinements/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { GraphState } from '../../../../types'; + +export const discardPreviousRefinements = ({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected, + state, +}: { + generationAttempts: number; + hallucinationFailures: number; + isHallucinationDetected: boolean; + state: GraphState; +}): GraphState => { + return { + ...state, + combinedRefinements: '', // <-- reset the combined refinements + generationAttempts: generationAttempts + 1, + refinements: [], // <-- reset the refinements + hallucinationFailures: isHallucinationDetected + ? hallucinationFailures + 1 + : hallucinationFailures, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts new file mode 100644 index 0000000000000..11ea40a48ae55 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_combined_refine_prompt/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AttackDiscovery } from '@kbn/elastic-assistant-common'; +import { isEmpty } from 'lodash/fp'; + +import { getContinuePrompt } from '../../../helpers/get_continue_prompt'; + +/** + * Returns a prompt that combines the initial query, a refine prompt, and partial results + */ +export const getCombinedRefinePrompt = ({ + attackDiscoveryPrompt, + combinedRefinements, + refinePrompt, + unrefinedResults, +}: { + attackDiscoveryPrompt: string; + combinedRefinements: string; + refinePrompt: string; + unrefinedResults: AttackDiscovery[] | null; +}): string => { + const baseQuery = `${attackDiscoveryPrompt} + +${refinePrompt} + +""" +${JSON.stringify(unrefinedResults, null, 2)} +""" + +`; + + return isEmpty(combinedRefinements) + ? baseQuery // no partial results yet + : `${baseQuery} + +${getContinuePrompt()} + +""" +${combinedRefinements} +""" + +`; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts new file mode 100644 index 0000000000000..5743316669785 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_default_refine_prompt/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultRefinePrompt = + (): string => `You previously generated the following insights, but sometimes they represent the same attack. + +Combine the insights below, when they represent the same attack; leave any insights that are not combined unchanged:`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts new file mode 100644 index 0000000000000..13d0a2228a3ee --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/helpers/get_use_unrefined_results/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Note: the conditions tested here are different than the generate node + */ +export const getUseUnrefinedResults = ({ + maxHallucinationFailuresReached, + maxRetriesReached, +}: { + maxHallucinationFailuresReached: boolean; + maxRetriesReached: boolean; +}): boolean => maxRetriesReached || maxHallucinationFailuresReached; // we may have reached max halucination failures, but we still want to use the unrefined results diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts new file mode 100644 index 0000000000000..0c7987eef92bc --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/refine/index.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientLlm } from '@kbn/langchain/server'; +import type { Logger } from '@kbn/core/server'; + +import { discardPreviousRefinements } from './helpers/discard_previous_refinements'; +import { extractJson } from '../helpers/extract_json'; +import { getChainWithFormatInstructions } from '../helpers/get_chain_with_format_instructions'; +import { getCombined } from '../helpers/get_combined'; +import { getCombinedRefinePrompt } from './helpers/get_combined_refine_prompt'; +import { generationsAreRepeating } from '../helpers/generations_are_repeating'; +import { getMaxHallucinationFailuresReached } from '../../helpers/get_max_hallucination_failures_reached'; +import { getMaxRetriesReached } from '../../helpers/get_max_retries_reached'; +import { getUseUnrefinedResults } from './helpers/get_use_unrefined_results'; +import { parseCombinedOrThrow } from '../helpers/parse_combined_or_throw'; +import { responseIsHallucinated } from '../helpers/response_is_hallucinated'; +import type { GraphState } from '../../types'; + +export const getRefineNode = ({ + llm, + logger, +}: { + llm: ActionsClientLlm; + logger?: Logger; +}): ((state: GraphState) => Promise<GraphState>) => { + const refine = async (state: GraphState): Promise<GraphState> => { + logger?.debug(() => '---REFINE---'); + + const { + attackDiscoveryPrompt, + combinedRefinements, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + maxRepeatedGenerations, + refinements, + refinePrompt, + unrefinedResults, + } = state; + + let combinedResponse = ''; // mutable, because it must be accessed in the catch block + let partialResponse = ''; // mutable, because it must be accessed in the catch block + + try { + const query = getCombinedRefinePrompt({ + attackDiscoveryPrompt, + combinedRefinements, + refinePrompt, + unrefinedResults, + }); + + const { chain, formatInstructions, llmType } = getChainWithFormatInstructions(llm); + + logger?.debug( + () => `refine node is invoking the chain (${llmType}), attempt ${generationAttempts}` + ); + + const rawResponse = (await chain.invoke({ + format_instructions: formatInstructions, + query, + })) as unknown as string; + + // LOCAL MUTATION: + partialResponse = extractJson(rawResponse); // remove the surrounding ```json``` + + // if the response is hallucinated, discard it: + if (responseIsHallucinated(partialResponse)) { + logger?.debug( + () => + `refine node detected a hallucination (${llmType}), on attempt ${generationAttempts}; discarding the accumulated refinements and starting over` + ); + + return discardPreviousRefinements({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: true, + state, + }); + } + + // if the refinements are repeating, discard previous refinements and start over: + if ( + generationsAreRepeating({ + currentGeneration: partialResponse, + previousGenerations: refinements, + sampleLastNGenerations: maxRepeatedGenerations, + }) + ) { + logger?.debug( + () => + `refine node detected (${llmType}), detected ${maxRepeatedGenerations} repeated generations on attempt ${generationAttempts}; discarding the accumulated results and starting over` + ); + + // discard the accumulated results and start over: + return discardPreviousRefinements({ + generationAttempts, + hallucinationFailures, + isHallucinationDetected: false, + state, + }); + } + + // LOCAL MUTATION: + combinedResponse = getCombined({ combinedGenerations: combinedRefinements, partialResponse }); // combine the new response with the previous ones + + const attackDiscoveries = parseCombinedOrThrow({ + combinedResponse, + generationAttempts, + llmType, + logger, + nodeName: 'refine', + }); + + return { + ...state, + attackDiscoveries, // the final, refined answer + generationAttempts: generationAttempts + 1, + combinedRefinements: combinedResponse, + refinements: [...refinements, partialResponse], + }; + } catch (error) { + const parsingError = `refine node is unable to parse (${llm._llmType()}) response from attempt ${generationAttempts}; (this may be an incomplete response from the model): ${error}`; + logger?.debug(() => parsingError); // logged at debug level because the error is expected when the model returns an incomplete response + + const maxRetriesReached = getMaxRetriesReached({ + generationAttempts: generationAttempts + 1, + maxGenerationAttempts, + }); + + const maxHallucinationFailuresReached = getMaxHallucinationFailuresReached({ + hallucinationFailures, + maxHallucinationFailures, + }); + + // we will use the unrefined results if we have reached the maximum number of retries or hallucination failures: + const useUnrefinedResults = getUseUnrefinedResults({ + maxHallucinationFailuresReached, + maxRetriesReached, + }); + + if (useUnrefinedResults) { + logger?.debug( + () => + `refine node is using unrefined results response (${llm._llmType()}) from attempt ${generationAttempts}, because all attempts have been used` + ); + } + + return { + ...state, + attackDiscoveries: useUnrefinedResults ? unrefinedResults : null, + combinedRefinements: combinedResponse, + errors: [...state.errors, parsingError], + generationAttempts: generationAttempts + 1, + refinements: [...refinements, partialResponse], + }; + } + }; + + return refine; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts new file mode 100644 index 0000000000000..3a8b7ed3a6b94 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/anonymized_alerts_retriever/index.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core/server'; +import { Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { CallbackManagerForRetrieverRun } from '@langchain/core/callbacks/manager'; +import type { Document } from '@langchain/core/documents'; +import { BaseRetriever, type BaseRetrieverInput } from '@langchain/core/retrievers'; + +import { getAnonymizedAlerts } from '../helpers/get_anonymized_alerts'; + +export type CustomRetrieverInput = BaseRetrieverInput; + +export class AnonymizedAlertsRetriever extends BaseRetriever { + lc_namespace = ['langchain', 'retrievers']; + + #alertsIndexPattern?: string; + #anonymizationFields?: AnonymizationFieldResponse[]; + #esClient: ElasticsearchClient; + #onNewReplacements?: (newReplacements: Replacements) => void; + #replacements?: Replacements; + #size?: number; + + constructor({ + alertsIndexPattern, + anonymizationFields, + fields, + esClient, + onNewReplacements, + replacements, + size, + }: { + alertsIndexPattern?: string; + anonymizationFields?: AnonymizationFieldResponse[]; + fields?: CustomRetrieverInput; + esClient: ElasticsearchClient; + onNewReplacements?: (newReplacements: Replacements) => void; + replacements?: Replacements; + size?: number; + }) { + super(fields); + + this.#alertsIndexPattern = alertsIndexPattern; + this.#anonymizationFields = anonymizationFields; + this.#esClient = esClient; + this.#onNewReplacements = onNewReplacements; + this.#replacements = replacements; + this.#size = size; + } + + async _getRelevantDocuments( + query: string, + runManager?: CallbackManagerForRetrieverRun + ): Promise<Document[]> { + const anonymizedAlerts = await getAnonymizedAlerts({ + alertsIndexPattern: this.#alertsIndexPattern, + anonymizationFields: this.#anonymizationFields, + esClient: this.#esClient, + onNewReplacements: this.#onNewReplacements, + replacements: this.#replacements, + size: this.#size, + }); + + return anonymizedAlerts.map((alert) => ({ + pageContent: alert, + metadata: {}, + })); + } +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts similarity index 90% rename from x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts index 6b7526870eb9f..b616c392ddd21 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.test.ts @@ -6,19 +6,19 @@ */ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { getOpenAndAcknowledgedAlertsQuery } from '@kbn/elastic-assistant-common'; -import { getAnonymizedAlerts } from './get_anonymized_alerts'; -import { mockOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_open_and_acknowledged_alerts_query_results'; -import { getOpenAndAcknowledgedAlertsQuery } from '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query'; -import { MIN_SIZE } from '../open_and_acknowledged_alerts/helpers'; +const MIN_SIZE = 10; -jest.mock('../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query', () => { - const original = jest.requireActual( - '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query' - ); +import { getAnonymizedAlerts } from '.'; +import { mockOpenAndAcknowledgedAlertsQueryResults } from '../../../../mock/mock_open_and_acknowledged_alerts_query_results'; + +jest.mock('@kbn/elastic-assistant-common', () => { + const original = jest.requireActual('@kbn/elastic-assistant-common'); return { - getOpenAndAcknowledgedAlertsQuery: jest.fn(() => original), + ...original, + getOpenAndAcknowledgedAlertsQuery: jest.fn(), }; }); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts similarity index 77% rename from x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts index 5989caf439518..bc2a7f5bf9e71 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_anonymized_alerts.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/helpers/get_anonymized_alerts/index.ts @@ -7,12 +7,16 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { ElasticsearchClient } from '@kbn/core/server'; -import type { Replacements } from '@kbn/elastic-assistant-common'; -import { getAnonymizedValue, transformRawData } from '@kbn/elastic-assistant-common'; -import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import { + Replacements, + getAnonymizedValue, + getOpenAndAcknowledgedAlertsQuery, + getRawDataOrDefault, + sizeIsOutOfRange, + transformRawData, +} from '@kbn/elastic-assistant-common'; -import { getOpenAndAcknowledgedAlertsQuery } from '../open_and_acknowledged_alerts/get_open_and_acknowledged_alerts_query'; -import { getRawDataOrDefault, sizeIsOutOfRange } from '../open_and_acknowledged_alerts/helpers'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; export const getAnonymizedAlerts = async ({ alertsIndexPattern, diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts new file mode 100644 index 0000000000000..951ae3bca8854 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/nodes/retriever/index.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; + +import { AnonymizedAlertsRetriever } from './anonymized_alerts_retriever'; +import type { GraphState } from '../../types'; + +export const getRetrieveAnonymizedAlertsNode = ({ + alertsIndexPattern, + anonymizationFields, + esClient, + logger, + onNewReplacements, + replacements, + size, +}: { + alertsIndexPattern?: string; + anonymizationFields?: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + logger?: Logger; + onNewReplacements?: (replacements: Replacements) => void; + replacements?: Replacements; + size?: number; +}): ((state: GraphState) => Promise<GraphState>) => { + let localReplacements = { ...(replacements ?? {}) }; + const localOnNewReplacements = (newReplacements: Replacements) => { + localReplacements = { ...localReplacements, ...newReplacements }; + + onNewReplacements?.(localReplacements); // invoke the callback with the latest replacements + }; + + const retriever = new AnonymizedAlertsRetriever({ + alertsIndexPattern, + anonymizationFields, + esClient, + onNewReplacements: localOnNewReplacements, + replacements, + size, + }); + + const retrieveAnonymizedAlerts = async (state: GraphState): Promise<GraphState> => { + logger?.debug(() => '---RETRIEVE ANONYMIZED ALERTS---'); + const documents = await retriever + .withConfig({ runName: 'runAnonymizedAlertsRetriever' }) + .invoke(''); + + return { + ...state, + anonymizedAlerts: documents, + replacements: localReplacements, + }; + }; + + return retrieveAnonymizedAlerts; +}; + +/** + * Retrieve documents + * + * @param {GraphState} state The current state of the graph. + * @param {RunnableConfig | undefined} config The configuration object for tracing. + * @returns {Promise<GraphState>} The new state object. + */ diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts new file mode 100644 index 0000000000000..4229155cc2e25 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/state/index.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import type { Document } from '@langchain/core/documents'; +import type { StateGraphArgs } from '@langchain/langgraph'; + +import { + DEFAULT_MAX_GENERATION_ATTEMPTS, + DEFAULT_MAX_HALLUCINATION_FAILURES, + DEFAULT_MAX_REPEATED_GENERATIONS, +} from '../constants'; +import { getDefaultAttackDiscoveryPrompt } from '../nodes/helpers/get_default_attack_discovery_prompt'; +import { getDefaultRefinePrompt } from '../nodes/refine/helpers/get_default_refine_prompt'; +import type { GraphState } from '../types'; + +export const getDefaultGraphState = (): StateGraphArgs<GraphState>['channels'] => ({ + attackDiscoveries: { + value: (x: AttackDiscovery[] | null, y?: AttackDiscovery[] | null) => y ?? x, + default: () => null, + }, + attackDiscoveryPrompt: { + value: (x: string, y?: string) => y ?? x, + default: () => getDefaultAttackDiscoveryPrompt(), + }, + anonymizedAlerts: { + value: (x: Document[], y?: Document[]) => y ?? x, + default: () => [], + }, + combinedGenerations: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + combinedRefinements: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + errors: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + generationAttempts: { + value: (x: number, y?: number) => y ?? x, + default: () => 0, + }, + generations: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + hallucinationFailures: { + value: (x: number, y?: number) => y ?? x, + default: () => 0, + }, + refinePrompt: { + value: (x: string, y?: string) => y ?? x, + default: () => getDefaultRefinePrompt(), + }, + maxGenerationAttempts: { + value: (x: number, y?: number) => y ?? x, + default: () => DEFAULT_MAX_GENERATION_ATTEMPTS, + }, + maxHallucinationFailures: { + value: (x: number, y?: number) => y ?? x, + default: () => DEFAULT_MAX_HALLUCINATION_FAILURES, + }, + maxRepeatedGenerations: { + value: (x: number, y?: number) => y ?? x, + default: () => DEFAULT_MAX_REPEATED_GENERATIONS, + }, + refinements: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + replacements: { + value: (x: Replacements, y?: Replacements) => y ?? x, + default: () => ({}), + }, + unrefinedResults: { + value: (x: AttackDiscovery[] | null, y?: AttackDiscovery[] | null) => y ?? x, + default: () => null, + }, +}); diff --git a/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts new file mode 100644 index 0000000000000..b4473a02b82ae --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/graphs/default_attack_discovery_graph/types.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import type { Document } from '@langchain/core/documents'; + +export interface GraphState { + attackDiscoveries: AttackDiscovery[] | null; + attackDiscoveryPrompt: string; + anonymizedAlerts: Document[]; + combinedGenerations: string; + combinedRefinements: string; + errors: string[]; + generationAttempts: number; + generations: string[]; + hallucinationFailures: number; + maxGenerationAttempts: number; + maxHallucinationFailures: number; + maxRepeatedGenerations: number; + refinements: string[]; + refinePrompt: string; + replacements: Replacements; + unrefinedResults: AttackDiscovery[] | null; +} diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts similarity index 94% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts index 6e9cc39597bd7..a82ec24c7041e 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.test.ts @@ -10,11 +10,11 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { createAttackDiscovery } from './create_attack_discovery'; import { AttackDiscoveryCreateProps, AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { getAttackDiscovery } from './get_attack_discovery'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; import { loggerMock } from '@kbn/logging-mocks'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); -jest.mock('./get_attack_discovery'); +jest.mock('../get_attack_discovery/get_attack_discovery'); const attackDiscoveryCreate: AttackDiscoveryCreateProps = { attackDiscoveries: [], apiConfig: { diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts index 7304ab3488529..fc511dc559d30 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/create_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/create_attack_discovery/create_attack_discovery.ts @@ -9,8 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryCreateProps, AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { getAttackDiscovery } from './get_attack_discovery'; -import { CreateAttackDiscoverySchema } from './types'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; +import { CreateAttackDiscoverySchema } from '../types'; export interface CreateAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/field_maps_configuration.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration.ts similarity index 100% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/field_maps_configuration.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/field_maps_configuration/field_maps_configuration.ts diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts index e80d1e4589838..945603b517938 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_all_attack_discoveries.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_all_attack_discoveries/find_all_attack_discoveries.ts @@ -8,8 +8,8 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { EsAttackDiscoverySchema } from './types'; -import { transformESSearchToAttackDiscovery } from './transforms'; +import { EsAttackDiscoverySchema } from '../types'; +import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; const MAX_ITEMS = 10000; export interface FindAllAttackDiscoveriesParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts index 10688ce25b25e..53d74e6e92f42 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts index 532c35ac89c05..07fde44080026 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/find_attack_discovery_by_connector_id.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id.ts @@ -7,8 +7,8 @@ import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from './types'; -import { transformESSearchToAttackDiscovery } from './transforms'; +import { EsAttackDiscoverySchema } from '../types'; +import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; export interface FindAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts index 4ee89fb7a3bc0..af1a1827cbddd 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.test.ts @@ -8,7 +8,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { getAttackDiscovery } from './get_attack_discovery'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; import { AuthenticatedUser } from '@kbn/core-security-common'; const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts index d0cf6fd19ae05..ae2051d9e480b 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/get_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/get_attack_discovery/get_attack_discovery.ts @@ -7,8 +7,8 @@ import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from './types'; -import { transformESSearchToAttackDiscovery } from './transforms'; +import { EsAttackDiscoverySchema } from '../types'; +import { transformESSearchToAttackDiscovery } from '../transforms/transforms'; export interface GetAttackDiscoveryParams { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts index ca053743c8035..5aac100f5f52c 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/index.ts @@ -11,12 +11,15 @@ import { AttackDiscoveryResponse, } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { findAllAttackDiscoveries } from './find_all_attack_discoveries'; -import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id'; -import { updateAttackDiscovery } from './update_attack_discovery'; -import { createAttackDiscovery } from './create_attack_discovery'; -import { getAttackDiscovery } from './get_attack_discovery'; -import { AIAssistantDataClient, AIAssistantDataClientParams } from '..'; +import { findAllAttackDiscoveries } from './find_all_attack_discoveries/find_all_attack_discoveries'; +import { findAttackDiscoveryByConnectorId } from './find_attack_discovery_by_connector_id/find_attack_discovery_by_connector_id'; +import { updateAttackDiscovery } from './update_attack_discovery/update_attack_discovery'; +import { createAttackDiscovery } from './create_attack_discovery/create_attack_discovery'; +import { getAttackDiscovery } from './get_attack_discovery/get_attack_discovery'; +import { + AIAssistantDataClient, + AIAssistantDataClientParams, +} from '../../../ai_assistant_data_clients'; type AttackDiscoveryDataClientParams = AIAssistantDataClientParams; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts similarity index 98% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts index d9a37582f48b0..765d40f7a3226 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/transforms.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts @@ -7,7 +7,7 @@ import { estypes } from '@elastic/elasticsearch'; import { AttackDiscoveryResponse } from '@kbn/elastic-assistant-common'; -import { EsAttackDiscoverySchema } from './types'; +import { EsAttackDiscoverySchema } from '../types'; export const transformESSearchToAttackDiscovery = ( response: estypes.SearchResponse<EsAttackDiscoverySchema> diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts similarity index 93% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts index 4a17c50e06af4..08be262fede5a 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/types.ts @@ -6,7 +6,7 @@ */ import { AttackDiscoveryStatus, Provider } from '@kbn/elastic-assistant-common'; -import { EsReplacementSchema } from '../conversations/types'; +import { EsReplacementSchema } from '../../../ai_assistant_data_clients/conversations/types'; export interface EsAttackDiscoverySchema { '@timestamp': string; @@ -53,7 +53,7 @@ export interface CreateAttackDiscoverySchema { title: string; timestamp: string; details_markdown: string; - entity_summary_markdown: string; + entity_summary_markdown?: string; mitre_attack_tactics?: string[]; summary_markdown: string; id?: string; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts similarity index 97% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts index 24deda445f320..8d98839c092aa 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.test.ts @@ -7,7 +7,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import { getAttackDiscovery } from './get_attack_discovery'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; import { updateAttackDiscovery } from './update_attack_discovery'; import { AttackDiscoveryResponse, @@ -15,7 +15,7 @@ import { AttackDiscoveryUpdateProps, } from '@kbn/elastic-assistant-common'; import { AuthenticatedUser } from '@kbn/core-security-common'; -jest.mock('./get_attack_discovery'); +jest.mock('../get_attack_discovery/get_attack_discovery'); const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockLogger = loggerMock.create(); const user = { diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts similarity index 95% rename from x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts index 73a386bbb4362..c810a71c5f1a3 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/attack_discovery/update_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/attack_discovery/persistence/update_attack_discovery/update_attack_discovery.ts @@ -14,8 +14,8 @@ import { UUID, } from '@kbn/elastic-assistant-common'; import * as uuid from 'uuid'; -import { EsReplacementSchema } from '../conversations/types'; -import { getAttackDiscovery } from './get_attack_discovery'; +import { EsReplacementSchema } from '../../../../ai_assistant_data_clients/conversations/types'; +import { getAttackDiscovery } from '../get_attack_discovery/get_attack_discovery'; export interface UpdateAttackDiscoverySchema { id: UUID; @@ -25,7 +25,7 @@ export interface UpdateAttackDiscoverySchema { title: string; timestamp: string; details_markdown: string; - entity_summary_markdown: string; + entity_summary_markdown?: string; mitre_attack_tactics?: string[]; summary_markdown: string; id?: string; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts index 706da7197f31a..b9e4f85a800a0 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/index.ts @@ -10,14 +10,41 @@ import { GetDefaultAssistantGraphParams, DefaultAssistantGraph, } from './default_assistant_graph/graph'; +import { + DefaultAttackDiscoveryGraph, + GetDefaultAttackDiscoveryGraphParams, + getDefaultAttackDiscoveryGraph, +} from '../../attack_discovery/graphs/default_attack_discovery_graph'; export type GetAssistantGraph = (params: GetDefaultAssistantGraphParams) => DefaultAssistantGraph; +export type GetAttackDiscoveryGraph = ( + params: GetDefaultAttackDiscoveryGraphParams +) => DefaultAttackDiscoveryGraph; + +export type GraphType = 'assistant' | 'attack-discovery'; + +export interface AssistantGraphMetadata { + getDefaultAssistantGraph: GetAssistantGraph; + graphType: 'assistant'; +} + +export interface AttackDiscoveryGraphMetadata { + getDefaultAttackDiscoveryGraph: GetAttackDiscoveryGraph; + graphType: 'attack-discovery'; +} + +export type GraphMetadata = AssistantGraphMetadata | AttackDiscoveryGraphMetadata; /** * Map of the different Assistant Graphs. Useful for running evaluations. */ -export const ASSISTANT_GRAPH_MAP: Record<string, GetAssistantGraph> = { - DefaultAssistantGraph: getDefaultAssistantGraph, - // TODO: Support additional graphs - // AttackDiscoveryGraph: getDefaultAssistantGraph, +export const ASSISTANT_GRAPH_MAP: Record<string, GraphMetadata> = { + DefaultAssistantGraph: { + getDefaultAssistantGraph, + graphType: 'assistant', + }, + DefaultAttackDiscoveryGraph: { + getDefaultAttackDiscoveryGraph, + graphType: 'attack-discovery', + }, }; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts similarity index 85% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts index 74cf160c43ffe..ce07d66b9606e 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.test.ts @@ -8,15 +8,24 @@ import { getAttackDiscoveryRoute } from './get_attack_discovery'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { serverMock } from '../../__mocks__/server'; -import { requestContextMock } from '../../__mocks__/request_context'; +import { serverMock } from '../../../__mocks__/server'; +import { requestContextMock } from '../../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { getAttackDiscoveryRequest } from '../../__mocks__/request'; -import { getAttackDiscoveryStats, updateAttackDiscoveryLastViewedAt } from './helpers'; -jest.mock('./helpers'); +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; +import { getAttackDiscoveryRequest } from '../../../__mocks__/request'; +import { getAttackDiscoveryStats, updateAttackDiscoveryLastViewedAt } from '../helpers/helpers'; + +jest.mock('../helpers/helpers', () => { + const original = jest.requireActual('../helpers/helpers'); + + return { + ...original, + getAttackDiscoveryStats: jest.fn(), + updateAttackDiscoveryLastViewedAt: jest.fn(), + }; +}); const mockStats = { newConnectorResultsCount: 2, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts similarity index 92% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts index 09b2df98fe090..e3756b10a3fb3 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/get/get_attack_discovery.ts @@ -14,10 +14,10 @@ import { } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateAttackDiscoveryLastViewedAt, getAttackDiscoveryStats } from './helpers'; -import { ATTACK_DISCOVERY_BY_CONNECTOR_ID } from '../../../common/constants'; -import { buildResponse } from '../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { updateAttackDiscoveryLastViewedAt, getAttackDiscoveryStats } from '../helpers/helpers'; +import { ATTACK_DISCOVERY_BY_CONNECTOR_ID } from '../../../../common/constants'; +import { buildResponse } from '../../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../../types'; export const getAttackDiscoveryRoute = (router: IRouter<ElasticAssistantRequestHandlerContext>) => { router.versioned diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts deleted file mode 100644 index d5eaf7d159618..0000000000000 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts +++ /dev/null @@ -1,805 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AuthenticatedUser } from '@kbn/core-security-common'; -import moment from 'moment'; -import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; - -import { - REQUIRED_FOR_ATTACK_DISCOVERY, - addGenerationInterval, - attackDiscoveryStatus, - getAssistantToolParams, - handleToolError, - updateAttackDiscoveryStatusToCanceled, - updateAttackDiscoveryStatusToRunning, - updateAttackDiscoveries, - getAttackDiscoveryStats, -} from './helpers'; -import { ActionsClientLlm } from '@kbn/langchain/server'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; -import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { loggerMock } from '@kbn/logging-mocks'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { - AttackDiscoveryPostRequestBody, - ExecuteConnectorRequestBody, -} from '@kbn/elastic-assistant-common'; -import { coreMock } from '@kbn/core/server/mocks'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; - -import { - getAnonymizationFieldMock, - getUpdateAnonymizationFieldSchemaMock, -} from '../../__mocks__/anonymization_fields_schema.mock'; - -jest.mock('lodash/fp', () => ({ - uniq: jest.fn((arr) => Array.from(new Set(arr))), -})); - -jest.mock('@kbn/securitysolution-es-utils', () => ({ - transformError: jest.fn((err) => err), -})); -jest.mock('@kbn/langchain/server', () => ({ - ActionsClientLlm: jest.fn(), -})); -jest.mock('../evaluate/utils', () => ({ - getLangSmithTracer: jest.fn().mockReturnValue([]), -})); -jest.mock('../utils', () => ({ - getLlmType: jest.fn().mockReturnValue('llm-type'), -})); -const findAttackDiscoveryByConnectorId = jest.fn(); -const updateAttackDiscovery = jest.fn(); -const createAttackDiscovery = jest.fn(); -const getAttackDiscovery = jest.fn(); -const findAllAttackDiscoveries = jest.fn(); -const mockDataClient = { - findAttackDiscoveryByConnectorId, - updateAttackDiscovery, - createAttackDiscovery, - getAttackDiscovery, - findAllAttackDiscoveries, -} as unknown as AttackDiscoveryDataClient; -const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); -const mockLogger = loggerMock.create(); -const mockTelemetry = coreMock.createSetup().analytics; -const mockError = new Error('Test error'); - -const mockAuthenticatedUser = { - username: 'user', - profile_uid: '1234', - authentication_realm: { - type: 'my_realm_type', - name: 'my_realm_name', - }, -} as AuthenticatedUser; - -const mockApiConfig = { - connectorId: 'connector-id', - actionTypeId: '.bedrock', - model: 'model', - provider: OpenAiProviderType.OpenAi, -}; - -const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockRequest: KibanaRequest<unknown, unknown, any, any> = {} as unknown as KibanaRequest< - unknown, - unknown, - any, // eslint-disable-line @typescript-eslint/no-explicit-any - any // eslint-disable-line @typescript-eslint/no-explicit-any ->; - -describe('helpers', () => { - const date = '2024-03-28T22:27:28.000Z'; - beforeAll(() => { - jest.useFakeTimers(); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - beforeEach(() => { - jest.clearAllMocks(); - jest.setSystemTime(new Date(date)); - getAttackDiscovery.mockResolvedValue(mockCurrentAd); - updateAttackDiscovery.mockResolvedValue({}); - }); - describe('getAssistantToolParams', () => { - const alertsIndexPattern = '.alerts-security.alerts-default'; - const esClient = elasticsearchClientMock.createElasticsearchClient(); - const actionsClient = actionsClientMock.create(); - const langChainTimeout = 1000; - const latestReplacements = {}; - const llm = new ActionsClientLlm({ - actionsClient, - connectorId: 'test-connecter-id', - llmType: 'bedrock', - logger: mockLogger, - temperature: 0, - timeout: 580000, - }); - const onNewReplacements = jest.fn(); - const size = 20; - - const mockParams = { - actionsClient, - alertsIndexPattern: 'alerts-*', - anonymizationFields: [{ id: '1', field: 'field1', allowed: true, anonymized: true }], - apiConfig: mockApiConfig, - esClient: mockEsClient, - connectorTimeout: 1000, - langChainTimeout: 2000, - langSmithProject: 'project', - langSmithApiKey: 'api-key', - logger: mockLogger, - latestReplacements: {}, - onNewReplacements: jest.fn(), - request: {} as KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >, - size: 10, - }; - - it('should return formatted assistant tool params', () => { - const result = getAssistantToolParams(mockParams); - - expect(ActionsClientLlm).toHaveBeenCalledWith( - expect.objectContaining({ - connectorId: 'connector-id', - llmType: 'llm-type', - }) - ); - expect(result.anonymizationFields).toEqual([ - ...mockParams.anonymizationFields, - ...REQUIRED_FOR_ATTACK_DISCOVERY, - ]); - }); - - it('returns the expected AssistantToolParams when anonymizationFields are provided', () => { - const anonymizationFields = [ - getAnonymizationFieldMock(getUpdateAnonymizationFieldSchemaMock()), - ]; - - const result = getAssistantToolParams({ - actionsClient, - alertsIndexPattern, - apiConfig: mockApiConfig, - anonymizationFields, - connectorTimeout: 1000, - latestReplacements, - esClient, - langChainTimeout, - logger: mockLogger, - onNewReplacements, - request: mockRequest, - size, - }); - - expect(result).toEqual({ - alertsIndexPattern, - anonymizationFields: [...anonymizationFields, ...REQUIRED_FOR_ATTACK_DISCOVERY], - isEnabledKnowledgeBase: false, - chain: undefined, - esClient, - langChainTimeout, - llm, - logger: mockLogger, - onNewReplacements, - replacements: latestReplacements, - request: mockRequest, - size, - }); - }); - - it('returns the expected AssistantToolParams when anonymizationFields is undefined', () => { - const anonymizationFields = undefined; - - const result = getAssistantToolParams({ - actionsClient, - alertsIndexPattern, - apiConfig: mockApiConfig, - anonymizationFields, - connectorTimeout: 1000, - latestReplacements, - esClient, - langChainTimeout, - logger: mockLogger, - onNewReplacements, - request: mockRequest, - size, - }); - - expect(result).toEqual({ - alertsIndexPattern, - anonymizationFields: [...REQUIRED_FOR_ATTACK_DISCOVERY], - isEnabledKnowledgeBase: false, - chain: undefined, - esClient, - langChainTimeout, - llm, - logger: mockLogger, - onNewReplacements, - replacements: latestReplacements, - request: mockRequest, - size, - }); - }); - - describe('addGenerationInterval', () => { - const generationInterval = { date: '2024-01-01T00:00:00Z', durationMs: 1000 }; - const existingIntervals = [ - { date: '2024-01-02T00:00:00Z', durationMs: 2000 }, - { date: '2024-01-03T00:00:00Z', durationMs: 3000 }, - ]; - - it('should add new interval and maintain length within MAX_GENERATION_INTERVALS', () => { - const result = addGenerationInterval(existingIntervals, generationInterval); - expect(result.length).toBeLessThanOrEqual(5); - expect(result).toContain(generationInterval); - }); - - it('should remove the oldest interval if exceeding MAX_GENERATION_INTERVALS', () => { - const longExistingIntervals = [...Array(5)].map((_, i) => ({ - date: `2024-01-0${i + 2}T00:00:00Z`, - durationMs: (i + 2) * 1000, - })); - const result = addGenerationInterval(longExistingIntervals, generationInterval); - expect(result.length).toBe(5); - expect(result).not.toContain(longExistingIntervals[4]); - }); - }); - - describe('updateAttackDiscoveryStatusToRunning', () => { - it('should update existing attack discovery to running', async () => { - const existingAd = { id: 'existing-id', backingIndex: 'index' }; - findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); - updateAttackDiscovery.mockResolvedValue(existingAd); - - const result = await updateAttackDiscoveryStatusToRunning( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig - ); - - expect(findAttackDiscoveryByConnectorId).toHaveBeenCalledWith({ - connectorId: mockApiConfig.connectorId, - authenticatedUser: mockAuthenticatedUser, - }); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: expect.objectContaining({ - status: attackDiscoveryStatus.running, - }), - authenticatedUser: mockAuthenticatedUser, - }); - expect(result).toEqual({ attackDiscoveryId: existingAd.id, currentAd: existingAd }); - }); - - it('should create a new attack discovery if none exists', async () => { - const newAd = { id: 'new-id', backingIndex: 'index' }; - findAttackDiscoveryByConnectorId.mockResolvedValue(null); - createAttackDiscovery.mockResolvedValue(newAd); - - const result = await updateAttackDiscoveryStatusToRunning( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig - ); - - expect(createAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryCreate: expect.objectContaining({ - status: attackDiscoveryStatus.running, - }), - authenticatedUser: mockAuthenticatedUser, - }); - expect(result).toEqual({ attackDiscoveryId: newAd.id, currentAd: newAd }); - }); - - it('should throw an error if updating or creating attack discovery fails', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(null); - createAttackDiscovery.mockResolvedValue(null); - - await expect( - updateAttackDiscoveryStatusToRunning(mockDataClient, mockAuthenticatedUser, mockApiConfig) - ).rejects.toThrow('Could not create attack discovery for connectorId: connector-id'); - }); - }); - - describe('updateAttackDiscoveryStatusToCanceled', () => { - const existingAd = { - id: 'existing-id', - backingIndex: 'index', - status: attackDiscoveryStatus.running, - }; - it('should update existing attack discovery to canceled', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); - updateAttackDiscovery.mockResolvedValue(existingAd); - - const result = await updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ); - - expect(findAttackDiscoveryByConnectorId).toHaveBeenCalledWith({ - connectorId: mockApiConfig.connectorId, - authenticatedUser: mockAuthenticatedUser, - }); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: expect.objectContaining({ - status: attackDiscoveryStatus.canceled, - }), - authenticatedUser: mockAuthenticatedUser, - }); - expect(result).toEqual(existingAd); - }); - - it('should throw an error if attack discovery is not running', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue({ - ...existingAd, - status: attackDiscoveryStatus.succeeded, - }); - await expect( - updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ) - ).rejects.toThrow( - 'Connector id connector-id does not have a running attack discovery, and therefore cannot be canceled.' - ); - }); - - it('should throw an error if attack discovery does not exist', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(null); - await expect( - updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ) - ).rejects.toThrow('Could not find attack discovery for connector id: connector-id'); - }); - it('should throw error if updateAttackDiscovery returns null', async () => { - findAttackDiscoveryByConnectorId.mockResolvedValue(existingAd); - updateAttackDiscovery.mockResolvedValue(null); - - await expect( - updateAttackDiscoveryStatusToCanceled( - mockDataClient, - mockAuthenticatedUser, - mockApiConfig.connectorId - ) - ).rejects.toThrow('Could not update attack discovery for connector id: connector-id'); - }); - }); - - describe('updateAttackDiscoveries', () => { - const mockAttackDiscoveryId = 'attack-discovery-id'; - const mockLatestReplacements = {}; - const mockRawAttackDiscoveries = JSON.stringify({ - alertsContextCount: 5, - attackDiscoveries: [{ alertIds: ['alert-1', 'alert-2'] }, { alertIds: ['alert-3'] }], - }); - const mockSize = 10; - const mockStartTime = moment('2024-03-28T22:25:28.000Z'); - - const mockArgs = { - apiConfig: mockApiConfig, - attackDiscoveryId: mockAttackDiscoveryId, - authenticatedUser: mockAuthenticatedUser, - dataClient: mockDataClient, - latestReplacements: mockLatestReplacements, - logger: mockLogger, - rawAttackDiscoveries: mockRawAttackDiscoveries, - size: mockSize, - startTime: mockStartTime, - telemetry: mockTelemetry, - }; - - it('should update attack discoveries and report success telemetry', async () => { - await updateAttackDiscoveries(mockArgs); - - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - alertsContextCount: 5, - attackDiscoveries: [{ alertIds: ['alert-1', 'alert-2'] }, { alertIds: ['alert-3'] }], - status: attackDiscoveryStatus.succeeded, - id: mockAttackDiscoveryId, - replacements: mockLatestReplacements, - backingIndex: mockCurrentAd.backingIndex, - generationIntervals: [ - { date, durationMs: 120000 }, - ...mockCurrentAd.generationIntervals, - ], - }, - authenticatedUser: mockAuthenticatedUser, - }); - - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_success', { - actionTypeId: mockApiConfig.actionTypeId, - alertsContextCount: 5, - alertsCount: 3, - configuredAlertsCount: mockSize, - discoveriesGenerated: 2, - durationMs: 120000, - model: mockApiConfig.model, - provider: mockApiConfig.provider, - }); - }); - - it('should update attack discoveries without generation interval if no discoveries are found', async () => { - const noDiscoveriesRaw = JSON.stringify({ - alertsContextCount: 0, - attackDiscoveries: [], - }); - - await updateAttackDiscoveries({ - ...mockArgs, - rawAttackDiscoveries: noDiscoveriesRaw, - }); - - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - alertsContextCount: 0, - attackDiscoveries: [], - status: attackDiscoveryStatus.succeeded, - id: mockAttackDiscoveryId, - replacements: mockLatestReplacements, - backingIndex: mockCurrentAd.backingIndex, - }, - authenticatedUser: mockAuthenticatedUser, - }); - - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_success', { - actionTypeId: mockApiConfig.actionTypeId, - alertsContextCount: 0, - alertsCount: 0, - configuredAlertsCount: mockSize, - discoveriesGenerated: 0, - durationMs: 120000, - model: mockApiConfig.model, - provider: mockApiConfig.provider, - }); - }); - - it('should catch and log an error if raw attack discoveries is null', async () => { - await updateAttackDiscoveries({ - ...mockArgs, - rawAttackDiscoveries: null, - }); - expect(mockLogger.error).toHaveBeenCalledTimes(1); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: 'tool returned no attack discoveries', - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - - it('should return and not call updateAttackDiscovery when getAttackDiscovery returns a canceled response', async () => { - getAttackDiscovery.mockResolvedValue({ - ...mockCurrentAd, - status: attackDiscoveryStatus.canceled, - }); - await updateAttackDiscoveries(mockArgs); - - expect(mockLogger.error).not.toHaveBeenCalled(); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - }); - - it('should log the error and report telemetry when getAttackDiscovery rejects', async () => { - getAttackDiscovery.mockRejectedValue(mockError); - await updateAttackDiscoveries(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - }); - - describe('handleToolError', () => { - const mockArgs = { - apiConfig: mockApiConfig, - attackDiscoveryId: 'discovery-id', - authenticatedUser: mockAuthenticatedUser, - backingIndex: 'backing-index', - dataClient: mockDataClient, - err: mockError, - latestReplacements: {}, - logger: mockLogger, - telemetry: mockTelemetry, - }; - - it('should log the error and update attack discovery status to failed', async () => { - await handleToolError(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - status: attackDiscoveryStatus.failed, - attackDiscoveries: [], - backingIndex: 'foo', - failureReason: 'Test error', - id: 'discovery-id', - replacements: {}, - }, - authenticatedUser: mockArgs.authenticatedUser, - }); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - - it('should log the error and report telemetry when updateAttackDiscovery rejects', async () => { - updateAttackDiscovery.mockRejectedValue(mockError); - await handleToolError(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).toHaveBeenCalledWith({ - attackDiscoveryUpdateProps: { - status: attackDiscoveryStatus.failed, - attackDiscoveries: [], - backingIndex: 'foo', - failureReason: 'Test error', - id: 'discovery-id', - replacements: {}, - }, - authenticatedUser: mockArgs.authenticatedUser, - }); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - - it('should return and not call updateAttackDiscovery when getAttackDiscovery returns a canceled response', async () => { - getAttackDiscovery.mockResolvedValue({ - ...mockCurrentAd, - status: attackDiscoveryStatus.canceled, - }); - await handleToolError(mockArgs); - - expect(mockTelemetry.reportEvent).not.toHaveBeenCalled(); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - }); - - it('should log the error and report telemetry when getAttackDiscovery rejects', async () => { - getAttackDiscovery.mockRejectedValue(mockError); - await handleToolError(mockArgs); - - expect(mockLogger.error).toHaveBeenCalledWith(mockError); - expect(updateAttackDiscovery).not.toHaveBeenCalled(); - expect(mockTelemetry.reportEvent).toHaveBeenCalledWith('attack_discovery_error', { - actionTypeId: mockArgs.apiConfig.actionTypeId, - errorMessage: mockError.message, - model: mockArgs.apiConfig.model, - provider: mockArgs.apiConfig.provider, - }); - }); - }); - }); - describe('getAttackDiscoveryStats', () => { - const mockDiscoveries = [ - { - timestamp: '2024-06-13T17:55:11.360Z', - id: '8abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:55:11.360Z', - updatedAt: '2024-06-17T20:47:57.556Z', - lastViewedAt: '2024-06-17T20:47:57.556Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'failed', - alertsContextCount: undefined, - apiConfig: { - connectorId: 'my-bedrock-old', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: - 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', - }, - { - timestamp: '2024-06-13T17:55:11.360Z', - id: '9abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:55:11.360Z', - updatedAt: '2024-06-17T20:47:57.556Z', - lastViewedAt: '2024-06-17T20:46:57.556Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'failed', - alertsContextCount: undefined, - apiConfig: { - connectorId: 'my-bedrock-old', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: - 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', - }, - { - timestamp: '2024-06-12T19:54:50.428Z', - id: '745e005b-7248-4c08-b8b6-4cad263b4be0', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T19:54:50.428Z', - updatedAt: '2024-06-17T20:47:27.182Z', - lastViewedAt: '2024-06-17T20:27:27.182Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'running', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-gen-ai', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-13T17:50:59.409Z', - id: 'f48da2ca-b63e-4387-82d7-1423a68500aa', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-13T17:50:59.409Z', - updatedAt: '2024-06-17T20:47:59.969Z', - lastViewedAt: '2024-06-17T20:47:35.227Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'succeeded', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-gpt4o-ai', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-12T21:18:56.377Z', - id: '82fced1d-de48-42db-9f56-e45122dee017', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T21:18:56.377Z', - updatedAt: '2024-06-17T20:47:39.372Z', - lastViewedAt: '2024-06-17T20:47:39.372Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'canceled', - alertsContextCount: 20, - apiConfig: { - connectorId: 'my-bedrock', - actionTypeId: '.bedrock', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: mockCurrentAd.attackDiscoveries, - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: undefined, - }, - { - timestamp: '2024-06-12T16:44:23.107Z', - id: 'a4709094-6116-484b-b096-1e8d151cb4b7', - backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', - createdAt: '2024-06-12T16:44:23.107Z', - updatedAt: '2024-06-17T20:48:16.961Z', - lastViewedAt: '2024-06-17T20:47:16.961Z', - users: [mockAuthenticatedUser], - namespace: 'default', - status: 'succeeded', - alertsContextCount: 0, - apiConfig: { - connectorId: 'my-gen-a2i', - actionTypeId: '.gen-ai', - defaultSystemPromptId: undefined, - model: undefined, - provider: undefined, - }, - attackDiscoveries: [ - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ...mockCurrentAd.attackDiscoveries, - ], - replacements: {}, - generationIntervals: mockCurrentAd.generationIntervals, - averageIntervalMs: mockCurrentAd.averageIntervalMs, - failureReason: 'steph threw an error', - }, - ]; - beforeEach(() => { - findAllAttackDiscoveries.mockResolvedValue(mockDiscoveries); - }); - it('returns the formatted stats object', async () => { - const stats = await getAttackDiscoveryStats({ - authenticatedUser: mockAuthenticatedUser, - dataClient: mockDataClient, - }); - expect(stats).toEqual([ - { - hasViewed: true, - status: 'failed', - count: 0, - connectorId: 'my-bedrock-old', - }, - { - hasViewed: false, - status: 'failed', - count: 0, - connectorId: 'my-bedrock-old', - }, - { - hasViewed: false, - status: 'running', - count: 1, - connectorId: 'my-gen-ai', - }, - { - hasViewed: false, - status: 'succeeded', - count: 1, - connectorId: 'my-gpt4o-ai', - }, - { - hasViewed: true, - status: 'canceled', - count: 1, - connectorId: 'my-bedrock', - }, - { - hasViewed: false, - status: 'succeeded', - count: 4, - connectorId: 'my-gen-a2i', - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts new file mode 100644 index 0000000000000..2e0a545eb083a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.test.ts @@ -0,0 +1,273 @@ +/* + * 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 { AuthenticatedUser } from '@kbn/core-security-common'; + +import { getAttackDiscoveryStats } from './helpers'; +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; + +jest.mock('lodash/fp', () => ({ + uniq: jest.fn((arr) => Array.from(new Set(arr))), +})); + +jest.mock('@kbn/securitysolution-es-utils', () => ({ + transformError: jest.fn((err) => err), +})); +jest.mock('@kbn/langchain/server', () => ({ + ActionsClientLlm: jest.fn(), +})); +jest.mock('../../evaluate/utils', () => ({ + getLangSmithTracer: jest.fn().mockReturnValue([]), +})); +jest.mock('../../utils', () => ({ + getLlmType: jest.fn().mockReturnValue('llm-type'), +})); +const findAttackDiscoveryByConnectorId = jest.fn(); +const updateAttackDiscovery = jest.fn(); +const createAttackDiscovery = jest.fn(); +const getAttackDiscovery = jest.fn(); +const findAllAttackDiscoveries = jest.fn(); +const mockDataClient = { + findAttackDiscoveryByConnectorId, + updateAttackDiscovery, + createAttackDiscovery, + getAttackDiscovery, + findAllAttackDiscoveries, +} as unknown as AttackDiscoveryDataClient; + +const mockAuthenticatedUser = { + username: 'user', + profile_uid: '1234', + authentication_realm: { + type: 'my_realm_type', + name: 'my_realm_name', + }, +} as AuthenticatedUser; + +const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; + +describe('helpers', () => { + const date = '2024-03-28T22:27:28.000Z'; + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + beforeEach(() => { + jest.clearAllMocks(); + jest.setSystemTime(new Date(date)); + getAttackDiscovery.mockResolvedValue(mockCurrentAd); + updateAttackDiscovery.mockResolvedValue({}); + }); + + describe('getAttackDiscoveryStats', () => { + const mockDiscoveries = [ + { + timestamp: '2024-06-13T17:55:11.360Z', + id: '8abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:55:11.360Z', + updatedAt: '2024-06-17T20:47:57.556Z', + lastViewedAt: '2024-06-17T20:47:57.556Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'failed', + alertsContextCount: undefined, + apiConfig: { + connectorId: 'my-bedrock-old', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: + 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', + }, + { + timestamp: '2024-06-13T17:55:11.360Z', + id: '9abb49bd-2f5d-43d2-bc2f-dd3c3cab25ad', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:55:11.360Z', + updatedAt: '2024-06-17T20:47:57.556Z', + lastViewedAt: '2024-06-17T20:46:57.556Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'failed', + alertsContextCount: undefined, + apiConfig: { + connectorId: 'my-bedrock-old', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: + 'ActionsClientLlm: action result status is error: an error occurred while running the action - Response validation failed (Error: [usage.input_tokens]: expected value of type [number] but got [undefined])', + }, + { + timestamp: '2024-06-12T19:54:50.428Z', + id: '745e005b-7248-4c08-b8b6-4cad263b4be0', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T19:54:50.428Z', + updatedAt: '2024-06-17T20:47:27.182Z', + lastViewedAt: '2024-06-17T20:27:27.182Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'running', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-gen-ai', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-13T17:50:59.409Z', + id: 'f48da2ca-b63e-4387-82d7-1423a68500aa', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-13T17:50:59.409Z', + updatedAt: '2024-06-17T20:47:59.969Z', + lastViewedAt: '2024-06-17T20:47:35.227Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'succeeded', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-gpt4o-ai', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-12T21:18:56.377Z', + id: '82fced1d-de48-42db-9f56-e45122dee017', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T21:18:56.377Z', + updatedAt: '2024-06-17T20:47:39.372Z', + lastViewedAt: '2024-06-17T20:47:39.372Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'canceled', + alertsContextCount: 20, + apiConfig: { + connectorId: 'my-bedrock', + actionTypeId: '.bedrock', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: mockCurrentAd.attackDiscoveries, + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: undefined, + }, + { + timestamp: '2024-06-12T16:44:23.107Z', + id: 'a4709094-6116-484b-b096-1e8d151cb4b7', + backingIndex: '.ds-.kibana-elastic-ai-assistant-attack-discovery-default-2024.06.12-000001', + createdAt: '2024-06-12T16:44:23.107Z', + updatedAt: '2024-06-17T20:48:16.961Z', + lastViewedAt: '2024-06-17T20:47:16.961Z', + users: [mockAuthenticatedUser], + namespace: 'default', + status: 'succeeded', + alertsContextCount: 0, + apiConfig: { + connectorId: 'my-gen-a2i', + actionTypeId: '.gen-ai', + defaultSystemPromptId: undefined, + model: undefined, + provider: undefined, + }, + attackDiscoveries: [ + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ...mockCurrentAd.attackDiscoveries, + ], + replacements: {}, + generationIntervals: mockCurrentAd.generationIntervals, + averageIntervalMs: mockCurrentAd.averageIntervalMs, + failureReason: 'steph threw an error', + }, + ]; + beforeEach(() => { + findAllAttackDiscoveries.mockResolvedValue(mockDiscoveries); + }); + it('returns the formatted stats object', async () => { + const stats = await getAttackDiscoveryStats({ + authenticatedUser: mockAuthenticatedUser, + dataClient: mockDataClient, + }); + expect(stats).toEqual([ + { + hasViewed: true, + status: 'failed', + count: 0, + connectorId: 'my-bedrock-old', + }, + { + hasViewed: false, + status: 'failed', + count: 0, + connectorId: 'my-bedrock-old', + }, + { + hasViewed: false, + status: 'running', + count: 1, + connectorId: 'my-gen-ai', + }, + { + hasViewed: false, + status: 'succeeded', + count: 1, + connectorId: 'my-gpt4o-ai', + }, + { + hasViewed: true, + status: 'canceled', + count: 1, + connectorId: 'my-bedrock', + }, + { + hasViewed: false, + status: 'succeeded', + count: 4, + connectorId: 'my-gen-a2i', + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts similarity index 55% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts index f016d6ac29118..188976f0b3f5c 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers/helpers.ts @@ -5,38 +5,29 @@ * 2.0. */ -import { AnalyticsServiceSetup, AuthenticatedUser, KibanaRequest, Logger } from '@kbn/core/server'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { AnalyticsServiceSetup, AuthenticatedUser, Logger } from '@kbn/core/server'; import { ApiConfig, AttackDiscovery, - AttackDiscoveryPostRequestBody, AttackDiscoveryResponse, AttackDiscoveryStat, AttackDiscoveryStatus, - ExecuteConnectorRequestBody, GenerationInterval, Replacements, } from '@kbn/elastic-assistant-common'; import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { Document } from '@langchain/core/documents'; import { v4 as uuidv4 } from 'uuid'; -import { ActionsClientLlm } from '@kbn/langchain/server'; - import { Moment } from 'moment'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { ActionsClient } from '@kbn/actions-plugin/server'; import moment from 'moment/moment'; import { uniq } from 'lodash/fp'; -import { PublicMethodsOf } from '@kbn/utility-types'; -import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; -import { getLlmType } from '../utils'; -import type { GetRegisteredTools } from '../../services/app_context'; + import { ATTACK_DISCOVERY_ERROR_EVENT, ATTACK_DISCOVERY_SUCCESS_EVENT, -} from '../../lib/telemetry/event_based_telemetry'; -import { AssistantToolParams } from '../../types'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; +} from '../../../lib/telemetry/event_based_telemetry'; +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ { @@ -53,116 +44,6 @@ export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ }, ]; -export const getAssistantToolParams = ({ - actionsClient, - alertsIndexPattern, - anonymizationFields, - apiConfig, - esClient, - connectorTimeout, - langChainTimeout, - langSmithProject, - langSmithApiKey, - logger, - latestReplacements, - onNewReplacements, - request, - size, -}: { - actionsClient: PublicMethodsOf<ActionsClient>; - alertsIndexPattern: string; - anonymizationFields?: AnonymizationFieldResponse[]; - apiConfig: ApiConfig; - esClient: ElasticsearchClient; - connectorTimeout: number; - langChainTimeout: number; - langSmithProject?: string; - langSmithApiKey?: string; - logger: Logger; - latestReplacements: Replacements; - onNewReplacements: (newReplacements: Replacements) => void; - request: KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >; - size: number; -}) => { - const traceOptions = { - projectName: langSmithProject, - tracers: [ - ...getLangSmithTracer({ - apiKey: langSmithApiKey, - projectName: langSmithProject, - logger, - }), - ], - }; - - const llm = new ActionsClientLlm({ - actionsClient, - connectorId: apiConfig.connectorId, - llmType: getLlmType(apiConfig.actionTypeId), - logger, - temperature: 0, // zero temperature for attack discovery, because we want structured JSON output - timeout: connectorTimeout, - traceOptions, - }); - - return formatAssistantToolParams({ - alertsIndexPattern, - anonymizationFields, - esClient, - latestReplacements, - langChainTimeout, - llm, - logger, - onNewReplacements, - request, - size, - }); -}; - -const formatAssistantToolParams = ({ - alertsIndexPattern, - anonymizationFields, - esClient, - langChainTimeout, - latestReplacements, - llm, - logger, - onNewReplacements, - request, - size, -}: { - alertsIndexPattern: string; - anonymizationFields?: AnonymizationFieldResponse[]; - esClient: ElasticsearchClient; - langChainTimeout: number; - latestReplacements: Replacements; - llm: ActionsClientLlm; - logger: Logger; - onNewReplacements: (newReplacements: Replacements) => void; - request: KibanaRequest< - unknown, - unknown, - ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody - >; - size: number; -}): Omit<AssistantToolParams, 'connectorId' | 'inference'> => ({ - alertsIndexPattern, - anonymizationFields: [...(anonymizationFields ?? []), ...REQUIRED_FOR_ATTACK_DISCOVERY], - isEnabledKnowledgeBase: false, // not required for attack discovery - esClient, - langChainTimeout, - llm, - logger, - onNewReplacements, - replacements: latestReplacements, - request, - size, -}); - export const attackDiscoveryStatus: { [k: string]: AttackDiscoveryStatus } = { canceled: 'canceled', failed: 'failed', @@ -187,7 +68,8 @@ export const addGenerationInterval = ( export const updateAttackDiscoveryStatusToRunning = async ( dataClient: AttackDiscoveryDataClient, authenticatedUser: AuthenticatedUser, - apiConfig: ApiConfig + apiConfig: ApiConfig, + alertsContextCount: number ): Promise<{ currentAd: AttackDiscoveryResponse; attackDiscoveryId: string; @@ -199,6 +81,7 @@ export const updateAttackDiscoveryStatusToRunning = async ( const currentAd = foundAttackDiscovery ? await dataClient?.updateAttackDiscovery({ attackDiscoveryUpdateProps: { + alertsContextCount, backingIndex: foundAttackDiscovery.backingIndex, id: foundAttackDiscovery.id, status: attackDiscoveryStatus.running, @@ -207,6 +90,7 @@ export const updateAttackDiscoveryStatusToRunning = async ( }) : await dataClient?.createAttackDiscovery({ attackDiscoveryCreate: { + alertsContextCount, apiConfig, attackDiscoveries: [], status: attackDiscoveryStatus.running, @@ -261,38 +145,32 @@ export const updateAttackDiscoveryStatusToCanceled = async ( return updatedAttackDiscovery; }; -const getDataFromJSON = (adStringified: string) => { - const { alertsContextCount, attackDiscoveries } = JSON.parse(adStringified); - return { alertsContextCount, attackDiscoveries }; -}; - export const updateAttackDiscoveries = async ({ + anonymizedAlerts, apiConfig, + attackDiscoveries, attackDiscoveryId, authenticatedUser, dataClient, latestReplacements, logger, - rawAttackDiscoveries, size, startTime, telemetry, }: { + anonymizedAlerts: Document[]; apiConfig: ApiConfig; + attackDiscoveries: AttackDiscovery[] | null; attackDiscoveryId: string; authenticatedUser: AuthenticatedUser; dataClient: AttackDiscoveryDataClient; latestReplacements: Replacements; logger: Logger; - rawAttackDiscoveries: string | null; size: number; startTime: Moment; telemetry: AnalyticsServiceSetup; }) => { try { - if (rawAttackDiscoveries == null) { - throw new Error('tool returned no attack discoveries'); - } const currentAd = await dataClient.getAttackDiscovery({ id: attackDiscoveryId, authenticatedUser, @@ -302,12 +180,12 @@ export const updateAttackDiscoveries = async ({ } const endTime = moment(); const durationMs = endTime.diff(startTime); - const { alertsContextCount, attackDiscoveries } = getDataFromJSON(rawAttackDiscoveries); + const alertsContextCount = anonymizedAlerts.length; const updateProps = { alertsContextCount, - attackDiscoveries, + attackDiscoveries: attackDiscoveries ?? undefined, status: attackDiscoveryStatus.succeeded, - ...(alertsContextCount === 0 || attackDiscoveries === 0 + ...(alertsContextCount === 0 ? {} : { generationIntervals: addGenerationInterval(currentAd.generationIntervals, { @@ -327,13 +205,14 @@ export const updateAttackDiscoveries = async ({ telemetry.reportEvent(ATTACK_DISCOVERY_SUCCESS_EVENT.eventType, { actionTypeId: apiConfig.actionTypeId, alertsContextCount: updateProps.alertsContextCount, - alertsCount: uniq( - updateProps.attackDiscoveries.flatMap( - (attackDiscovery: AttackDiscovery) => attackDiscovery.alertIds - ) - ).length, + alertsCount: + uniq( + updateProps.attackDiscoveries?.flatMap( + (attackDiscovery: AttackDiscovery) => attackDiscovery.alertIds + ) + ).length ?? 0, configuredAlertsCount: size, - discoveriesGenerated: updateProps.attackDiscoveries.length, + discoveriesGenerated: updateProps.attackDiscoveries?.length ?? 0, durationMs, model: apiConfig.model, provider: apiConfig.provider, @@ -350,70 +229,6 @@ export const updateAttackDiscoveries = async ({ } }; -export const handleToolError = async ({ - apiConfig, - attackDiscoveryId, - authenticatedUser, - dataClient, - err, - latestReplacements, - logger, - telemetry, -}: { - apiConfig: ApiConfig; - attackDiscoveryId: string; - authenticatedUser: AuthenticatedUser; - dataClient: AttackDiscoveryDataClient; - err: Error; - latestReplacements: Replacements; - logger: Logger; - telemetry: AnalyticsServiceSetup; -}) => { - try { - logger.error(err); - const error = transformError(err); - const currentAd = await dataClient.getAttackDiscovery({ - id: attackDiscoveryId, - authenticatedUser, - }); - - if (currentAd === null || currentAd?.status === 'canceled') { - return; - } - await dataClient.updateAttackDiscovery({ - attackDiscoveryUpdateProps: { - attackDiscoveries: [], - status: attackDiscoveryStatus.failed, - id: attackDiscoveryId, - replacements: latestReplacements, - backingIndex: currentAd.backingIndex, - failureReason: error.message, - }, - authenticatedUser, - }); - telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { - actionTypeId: apiConfig.actionTypeId, - errorMessage: error.message, - model: apiConfig.model, - provider: apiConfig.provider, - }); - } catch (updateErr) { - const updateError = transformError(updateErr); - telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { - actionTypeId: apiConfig.actionTypeId, - errorMessage: updateError.message, - model: apiConfig.model, - provider: apiConfig.provider, - }); - } -}; - -export const getAssistantTool = (getRegisteredTools: GetRegisteredTools, pluginName: string) => { - // get the attack discovery tool: - const assistantTools = getRegisteredTools(pluginName); - return assistantTools.find((tool) => tool.id === 'attack-discovery'); -}; - export const updateAttackDiscoveryLastViewedAt = async ({ connectorId, authenticatedUser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts similarity index 80% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts index 66aca77f1eb8b..9f5efbe5041d5 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.test.ts @@ -8,15 +8,23 @@ import { cancelAttackDiscoveryRoute } from './cancel_attack_discovery'; import { AuthenticatedUser } from '@kbn/core-security-common'; -import { serverMock } from '../../__mocks__/server'; -import { requestContextMock } from '../../__mocks__/request_context'; +import { serverMock } from '../../../../__mocks__/server'; +import { requestContextMock } from '../../../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { getCancelAttackDiscoveryRequest } from '../../__mocks__/request'; -import { updateAttackDiscoveryStatusToCanceled } from './helpers'; -jest.mock('./helpers'); +import { AttackDiscoveryDataClient } from '../../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../../__mocks__/attack_discovery_schema.mock'; +import { getCancelAttackDiscoveryRequest } from '../../../../__mocks__/request'; +import { updateAttackDiscoveryStatusToCanceled } from '../../helpers/helpers'; + +jest.mock('../../helpers/helpers', () => { + const original = jest.requireActual('../../helpers/helpers'); + + return { + ...original, + updateAttackDiscoveryStatusToCanceled: jest.fn(), + }; +}); const { clients, context } = requestContextMock.createTools(); const server: ReturnType<typeof serverMock.create> = serverMock.create(); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts similarity index 91% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts index 47b748c9c432a..86631708b1cf7 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/cancel_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/cancel/cancel_attack_discovery.ts @@ -14,16 +14,16 @@ import { } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateAttackDiscoveryStatusToCanceled } from './helpers'; -import { ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID } from '../../../common/constants'; -import { buildResponse } from '../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { updateAttackDiscoveryStatusToCanceled } from '../../helpers/helpers'; +import { ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID } from '../../../../../common/constants'; +import { buildResponse } from '../../../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../../../types'; export const cancelAttackDiscoveryRoute = ( router: IRouter<ElasticAssistantRequestHandlerContext> ) => { router.versioned - .put({ + .post({ access: 'internal', path: ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID, options: { diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx new file mode 100644 index 0000000000000..e58b67bdcc1ad --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/handle_graph_error/index.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AnalyticsServiceSetup, AuthenticatedUser, Logger } from '@kbn/core/server'; +import { ApiConfig, Replacements } from '@kbn/elastic-assistant-common'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +import { AttackDiscoveryDataClient } from '../../../../../lib/attack_discovery/persistence'; +import { attackDiscoveryStatus } from '../../../helpers/helpers'; +import { ATTACK_DISCOVERY_ERROR_EVENT } from '../../../../../lib/telemetry/event_based_telemetry'; + +export const handleGraphError = async ({ + apiConfig, + attackDiscoveryId, + authenticatedUser, + dataClient, + err, + latestReplacements, + logger, + telemetry, +}: { + apiConfig: ApiConfig; + attackDiscoveryId: string; + authenticatedUser: AuthenticatedUser; + dataClient: AttackDiscoveryDataClient; + err: Error; + latestReplacements: Replacements; + logger: Logger; + telemetry: AnalyticsServiceSetup; +}) => { + try { + logger.error(err); + const error = transformError(err); + const currentAd = await dataClient.getAttackDiscovery({ + id: attackDiscoveryId, + authenticatedUser, + }); + + if (currentAd === null || currentAd?.status === 'canceled') { + return; + } + + await dataClient.updateAttackDiscovery({ + attackDiscoveryUpdateProps: { + attackDiscoveries: [], + status: attackDiscoveryStatus.failed, + id: attackDiscoveryId, + replacements: latestReplacements, + backingIndex: currentAd.backingIndex, + failureReason: error.message, + }, + authenticatedUser, + }); + telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { + actionTypeId: apiConfig.actionTypeId, + errorMessage: error.message, + model: apiConfig.model, + provider: apiConfig.provider, + }); + } catch (updateErr) { + const updateError = transformError(updateErr); + telemetry.reportEvent(ATTACK_DISCOVERY_ERROR_EVENT.eventType, { + actionTypeId: apiConfig.actionTypeId, + errorMessage: updateError.message, + model: apiConfig.model, + provider: apiConfig.provider, + }); + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx new file mode 100644 index 0000000000000..8a8c49f796500 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/invoke_attack_discovery_graph/index.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger } from '@kbn/core/server'; +import { ApiConfig, AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import { ActionsClientLlm } from '@kbn/langchain/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import type { Document } from '@langchain/core/documents'; + +import { getDefaultAttackDiscoveryGraph } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph'; +import { + ATTACK_DISCOVERY_GRAPH_RUN_NAME, + ATTACK_DISCOVERY_TAG, +} from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/constants'; +import { GraphState } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/types'; +import { throwIfErrorCountsExceeded } from '../throw_if_error_counts_exceeded'; +import { getLlmType } from '../../../../utils'; + +export const invokeAttackDiscoveryGraph = async ({ + actionsClient, + alertsIndexPattern, + anonymizationFields, + apiConfig, + connectorTimeout, + esClient, + langSmithProject, + langSmithApiKey, + latestReplacements, + logger, + onNewReplacements, + size, +}: { + actionsClient: PublicMethodsOf<ActionsClient>; + alertsIndexPattern: string; + anonymizationFields: AnonymizationFieldResponse[]; + apiConfig: ApiConfig; + connectorTimeout: number; + esClient: ElasticsearchClient; + langSmithProject?: string; + langSmithApiKey?: string; + latestReplacements: Replacements; + logger: Logger; + onNewReplacements: (newReplacements: Replacements) => void; + size: number; +}): Promise<{ + anonymizedAlerts: Document[]; + attackDiscoveries: AttackDiscovery[] | null; +}> => { + const llmType = getLlmType(apiConfig.actionTypeId); + const model = apiConfig.model; + const tags = [ATTACK_DISCOVERY_TAG, llmType, model].flatMap((tag) => tag ?? []); + + const traceOptions = { + projectName: langSmithProject, + tracers: [ + ...getLangSmithTracer({ + apiKey: langSmithApiKey, + projectName: langSmithProject, + logger, + }), + ], + }; + + const llm = new ActionsClientLlm({ + actionsClient, + connectorId: apiConfig.connectorId, + llmType, + logger, + temperature: 0, // zero temperature for attack discovery, because we want structured JSON output + timeout: connectorTimeout, + traceOptions, + }); + + if (llm == null) { + throw new Error('LLM is required for attack discoveries'); + } + + const graph = getDefaultAttackDiscoveryGraph({ + alertsIndexPattern, + anonymizationFields, + esClient, + llm, + logger, + onNewReplacements, + replacements: latestReplacements, + size, + }); + + logger?.debug(() => 'invokeAttackDiscoveryGraph: invoking the Attack discovery graph'); + + const result: GraphState = await graph.invoke( + {}, + { + callbacks: [...(traceOptions?.tracers ?? [])], + runName: ATTACK_DISCOVERY_GRAPH_RUN_NAME, + tags, + } + ); + const { + attackDiscoveries, + anonymizedAlerts, + errors, + generationAttempts, + hallucinationFailures, + maxGenerationAttempts, + maxHallucinationFailures, + } = result; + + throwIfErrorCountsExceeded({ + errors, + generationAttempts, + hallucinationFailures, + logger, + maxGenerationAttempts, + maxHallucinationFailures, + }); + + return { anonymizedAlerts, attackDiscoveries }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx new file mode 100644 index 0000000000000..9cbf3fa06510d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.test.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; + +import { mockAnonymizationFields } from '../../../../../lib/attack_discovery/graphs/default_attack_discovery_graph/mock/mock_anonymization_fields'; +import { requestIsValid } from '.'; + +describe('requestIsValid', () => { + const alertsIndexPattern = '.alerts-security.alerts-default'; + const replacements = { uuid: 'original_value' }; + const size = 20; + const request = { + body: { + actionTypeId: '.bedrock', + alertsIndexPattern, + anonymizationFields: mockAnonymizationFields, + connectorId: 'test-connector-id', + replacements, + size, + subAction: 'invokeAI', + }, + } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; + + it('returns false when the request is missing required anonymization parameters', () => { + const requestMissingAnonymizationParams = { + body: { + alertsIndexPattern: '.alerts-security.alerts-default', + isEnabledKnowledgeBase: false, + size: 20, + }, + } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; + + const params = { + alertsIndexPattern, + request: requestMissingAnonymizationParams, // <-- missing required anonymization parameters + size, + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns false when the alertsIndexPattern is undefined', () => { + const params = { + alertsIndexPattern: undefined, // <-- alertsIndexPattern is undefined + request, + size, + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns false when size is undefined', () => { + const params = { + alertsIndexPattern, + request, + size: undefined, // <-- size is undefined + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns false when size is out of range', () => { + const params = { + alertsIndexPattern, + request, + size: 0, // <-- size is out of range + }; + + expect(requestIsValid(params)).toBe(false); + }); + + it('returns true if all required params are provided', () => { + const params = { + alertsIndexPattern, + request, + size, + }; + + expect(requestIsValid(params)).toBe(true); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx new file mode 100644 index 0000000000000..36487d8f6b3e2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/request_is_valid/index.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KibanaRequest } from '@kbn/core/server'; +import { + AttackDiscoveryPostRequestBody, + ExecuteConnectorRequestBody, + sizeIsOutOfRange, +} from '@kbn/elastic-assistant-common'; + +import { requestHasRequiredAnonymizationParams } from '../../../../../lib/langchain/helpers'; + +export const requestIsValid = ({ + alertsIndexPattern, + request, + size, +}: { + alertsIndexPattern: string | undefined; + request: KibanaRequest< + unknown, + unknown, + ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody + >; + size: number | undefined; +}): boolean => + requestHasRequiredAnonymizationParams(request) && + alertsIndexPattern != null && + size != null && + !sizeIsOutOfRange(size); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts new file mode 100644 index 0000000000000..409ee2da74cd2 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/index.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +import * as i18n from './translations'; + +export const throwIfErrorCountsExceeded = ({ + errors, + generationAttempts, + hallucinationFailures, + logger, + maxGenerationAttempts, + maxHallucinationFailures, +}: { + errors: string[]; + generationAttempts: number; + hallucinationFailures: number; + logger?: Logger; + maxGenerationAttempts: number; + maxHallucinationFailures: number; +}): void => { + if (hallucinationFailures >= maxHallucinationFailures) { + const hallucinationFailuresError = `${i18n.MAX_HALLUCINATION_FAILURES( + hallucinationFailures + )}\n${errors.join(',\n')}`; + + logger?.error(hallucinationFailuresError); + throw new Error(hallucinationFailuresError); + } + + if (generationAttempts >= maxGenerationAttempts) { + const generationAttemptsError = `${i18n.MAX_GENERATION_ATTEMPTS( + generationAttempts + )}\n${errors.join(',\n')}`; + + logger?.error(generationAttemptsError); + throw new Error(generationAttemptsError); + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts new file mode 100644 index 0000000000000..fbe06d0e73b2a --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/helpers/throw_if_error_counts_exceeded/translations.ts @@ -0,0 +1,28 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const MAX_HALLUCINATION_FAILURES = (hallucinationFailures: number) => + i18n.translate( + 'xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxHallucinationFailuresErrorMessage', + { + defaultMessage: + 'Maximum hallucination failures ({hallucinationFailures}) reached. Try sending fewer alerts to this model.', + values: { hallucinationFailures }, + } + ); + +export const MAX_GENERATION_ATTEMPTS = (generationAttempts: number) => + i18n.translate( + 'xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxGenerationAttemptsErrorMessage', + { + defaultMessage: + 'Maximum generation attempts ({generationAttempts}) reached. Try sending fewer alerts to this model.', + values: { generationAttempts }, + } + ); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts similarity index 79% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts index cbd3e6063fbd2..d50987317b0e3 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.test.ts @@ -7,22 +7,27 @@ import { AuthenticatedUser } from '@kbn/core-security-common'; import { postAttackDiscoveryRoute } from './post_attack_discovery'; -import { serverMock } from '../../__mocks__/server'; -import { requestContextMock } from '../../__mocks__/request_context'; +import { serverMock } from '../../../__mocks__/server'; +import { requestContextMock } from '../../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; -import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; -import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; -import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; -import { postAttackDiscoveryRequest } from '../../__mocks__/request'; +import { AttackDiscoveryDataClient } from '../../../lib/attack_discovery/persistence'; +import { transformESSearchToAttackDiscovery } from '../../../lib/attack_discovery/persistence/transforms/transforms'; +import { getAttackDiscoverySearchEsMock } from '../../../__mocks__/attack_discovery_schema.mock'; +import { postAttackDiscoveryRequest } from '../../../__mocks__/request'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; import { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; -import { - getAssistantTool, - getAssistantToolParams, - updateAttackDiscoveryStatusToRunning, -} from './helpers'; -jest.mock('./helpers'); + +import { updateAttackDiscoveryStatusToRunning } from '../helpers/helpers'; + +jest.mock('../helpers/helpers', () => { + const original = jest.requireActual('../helpers/helpers'); + + return { + ...original, + updateAttackDiscoveryStatusToRunning: jest.fn(), + }; +}); const { clients, context } = requestContextMock.createTools(); const server: ReturnType<typeof serverMock.create> = serverMock.create(); @@ -72,8 +77,6 @@ describe('postAttackDiscoveryRoute', () => { context.elasticAssistant.actions = actionsMock.createStart(); postAttackDiscoveryRoute(server.router); findAttackDiscoveryByConnectorId.mockResolvedValue(mockCurrentAd); - (getAssistantTool as jest.Mock).mockReturnValue({ getTool: jest.fn() }); - (getAssistantToolParams as jest.Mock).mockReturnValue({ tool: 'tool' }); (updateAttackDiscoveryStatusToRunning as jest.Mock).mockResolvedValue({ currentAd: runningAd, attackDiscoveryId: mockCurrentAd.id, @@ -117,15 +120,6 @@ describe('postAttackDiscoveryRoute', () => { }); }); - it('should handle assistantTool null response', async () => { - (getAssistantTool as jest.Mock).mockReturnValue(null); - const response = await server.inject( - postAttackDiscoveryRequest(mockRequestBody), - requestContextMock.convertContext(context) - ); - expect(response.status).toEqual(404); - }); - it('should handle updateAttackDiscoveryStatusToRunning error', async () => { (updateAttackDiscoveryStatusToRunning as jest.Mock).mockRejectedValue(new Error('Oh no!')); const response = await server.inject( diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts similarity index 79% rename from x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts rename to x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts index b9c680dde3d1d..b0273741bdf5e 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post/post_attack_discovery.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { type IKibanaResponse, IRouter, Logger } from '@kbn/core/server'; import { AttackDiscoveryPostRequestBody, @@ -13,20 +12,17 @@ import { ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION, Replacements, } from '@kbn/elastic-assistant-common'; +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { transformError } from '@kbn/securitysolution-es-utils'; import moment from 'moment/moment'; -import { ATTACK_DISCOVERY } from '../../../common/constants'; -import { - getAssistantTool, - getAssistantToolParams, - handleToolError, - updateAttackDiscoveries, - updateAttackDiscoveryStatusToRunning, -} from './helpers'; -import { DEFAULT_PLUGIN_NAME, getPluginNameFromRequest } from '../helpers'; -import { buildResponse } from '../../lib/build_response'; -import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { ATTACK_DISCOVERY } from '../../../../common/constants'; +import { handleGraphError } from './helpers/handle_graph_error'; +import { updateAttackDiscoveries, updateAttackDiscoveryStatusToRunning } from '../helpers/helpers'; +import { buildResponse } from '../../../lib/build_response'; +import { ElasticAssistantRequestHandlerContext } from '../../../types'; +import { invokeAttackDiscoveryGraph } from './helpers/invoke_attack_discovery_graph'; +import { requestIsValid } from './helpers/request_is_valid'; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds @@ -85,11 +81,6 @@ export const postAttackDiscoveryRoute = ( statusCode: 500, }); } - const pluginName = getPluginNameFromRequest({ - request, - defaultPluginName: DEFAULT_PLUGIN_NAME, - logger, - }); // get parameters from the request body const alertsIndexPattern = decodeURIComponent(request.body.alertsIndexPattern); @@ -102,6 +93,19 @@ export const postAttackDiscoveryRoute = ( size, } = request.body; + if ( + !requestIsValid({ + alertsIndexPattern, + request, + size, + }) + ) { + return resp.error({ + body: 'Bad Request', + statusCode: 400, + }); + } + // get an Elasticsearch client for the authenticated user: const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -111,59 +115,45 @@ export const postAttackDiscoveryRoute = ( latestReplacements = { ...latestReplacements, ...newReplacements }; }; - const assistantTool = getAssistantTool( - (await context.elasticAssistant).getRegisteredTools, - pluginName + const { currentAd, attackDiscoveryId } = await updateAttackDiscoveryStatusToRunning( + dataClient, + authenticatedUser, + apiConfig, + size ); - if (!assistantTool) { - return response.notFound(); // attack discovery tool not found - } - - const assistantToolParams = getAssistantToolParams({ + // Don't await the results of invoking the graph; (just the metadata will be returned from the route handler): + invokeAttackDiscoveryGraph({ actionsClient, alertsIndexPattern, anonymizationFields, apiConfig, - esClient, - latestReplacements, connectorTimeout: CONNECTOR_TIMEOUT, - langChainTimeout: LANG_CHAIN_TIMEOUT, + esClient, langSmithProject, langSmithApiKey, + latestReplacements, logger, onNewReplacements, - request, size, - }); - - // invoke the attack discovery tool: - const toolInstance = assistantTool.getTool(assistantToolParams); - - const { currentAd, attackDiscoveryId } = await updateAttackDiscoveryStatusToRunning( - dataClient, - authenticatedUser, - apiConfig - ); - - toolInstance - ?.invoke('') - .then((rawAttackDiscoveries: string) => + }) + .then(({ anonymizedAlerts, attackDiscoveries }) => updateAttackDiscoveries({ + anonymizedAlerts, apiConfig, + attackDiscoveries, attackDiscoveryId, authenticatedUser, dataClient, latestReplacements, logger, - rawAttackDiscoveries, size, startTime, telemetry, }) ) .catch((err) => - handleToolError({ + handleGraphError({ apiConfig, attackDiscoveryId, authenticatedUser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts new file mode 100644 index 0000000000000..c0320c9ff6adf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/get_graphs_from_names/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ASSISTANT_GRAPH_MAP, + AssistantGraphMetadata, + AttackDiscoveryGraphMetadata, +} from '../../../lib/langchain/graphs'; + +export interface GetGraphsFromNamesResults { + attackDiscoveryGraphs: AttackDiscoveryGraphMetadata[]; + assistantGraphs: AssistantGraphMetadata[]; +} + +export const getGraphsFromNames = (graphNames: string[]): GetGraphsFromNamesResults => + graphNames.reduce<GetGraphsFromNamesResults>( + (acc, graphName) => { + const graph = ASSISTANT_GRAPH_MAP[graphName]; + if (graph != null) { + return graph.graphType === 'assistant' + ? { ...acc, assistantGraphs: [...acc.assistantGraphs, graph] } + : { ...acc, attackDiscoveryGraphs: [...acc.attackDiscoveryGraphs, graph] }; + } + + return acc; + }, + { + attackDiscoveryGraphs: [], + assistantGraphs: [], + } + ); diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index 29a7527964677..eb12946a9b61f 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -29,6 +29,7 @@ import { createStructuredChatAgent, createToolCallingAgent, } from 'langchain/agents'; +import { omit } from 'lodash/fp'; import { buildResponse } from '../../lib/build_response'; import { AssistantDataClients } from '../../lib/langchain/executors/types'; import { AssistantToolParams, ElasticAssistantRequestHandlerContext, GetElser } from '../../types'; @@ -36,6 +37,7 @@ import { DEFAULT_PLUGIN_NAME, isV2KnowledgeBaseEnabled, performChecks } from '.. import { fetchLangSmithDataset } from './utils'; import { transformESSearchToAnonymizationFields } from '../../ai_assistant_data_clients/anonymization_fields/helpers'; import { EsAnonymizationFieldsSchema } from '../../ai_assistant_data_clients/anonymization_fields/types'; +import { evaluateAttackDiscovery } from '../../lib/attack_discovery/evaluation'; import { DefaultAssistantGraph, getDefaultAssistantGraph, @@ -47,9 +49,12 @@ import { structuredChatAgentPrompt, } from '../../lib/langchain/graphs/default_assistant_graph/prompts'; import { getLlmClass, getLlmType, isOpenSourceModel } from '../utils'; +import { getGraphsFromNames } from './get_graphs_from_names'; const DEFAULT_SIZE = 20; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes +const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds +const CONNECTOR_TIMEOUT = LANG_CHAIN_TIMEOUT - 10_000; // 9 minutes 40 seconds export const postEvaluateRoute = ( router: IRouter<ElasticAssistantRequestHandlerContext>, @@ -106,8 +111,10 @@ export const postEvaluateRoute = ( const { alertsIndexPattern, datasetName, + evaluatorConnectorId, graphs: graphNames, langSmithApiKey, + langSmithProject, connectorIds, size, replacements, @@ -124,7 +131,9 @@ export const postEvaluateRoute = ( logger.info('postEvaluateRoute:'); logger.info(`request.query:\n${JSON.stringify(request.query, null, 2)}`); - logger.info(`request.body:\n${JSON.stringify(request.body, null, 2)}`); + logger.info( + `request.body:\n${JSON.stringify(omit(['langSmithApiKey'], request.body), null, 2)}` + ); logger.info(`Evaluation ID: ${evaluationId}`); const totalExecutions = connectorIds.length * graphNames.length * dataset.length; @@ -170,6 +179,38 @@ export const postEvaluateRoute = ( // Fetch any tools registered to the security assistant const assistantTools = assistantContext.getRegisteredTools(DEFAULT_PLUGIN_NAME); + const { attackDiscoveryGraphs } = getGraphsFromNames(graphNames); + + if (attackDiscoveryGraphs.length > 0) { + try { + // NOTE: we don't wait for the evaluation to finish here, because + // the client will retry / timeout when evaluations take too long + void evaluateAttackDiscovery({ + actionsClient, + alertsIndexPattern, + attackDiscoveryGraphs, + connectors, + connectorTimeout: CONNECTOR_TIMEOUT, + datasetName, + esClient, + evaluationId, + evaluatorConnectorId, + langSmithApiKey, + langSmithProject, + logger, + runName, + size, + }); + } catch (err) { + logger.error(() => `Error evaluating attack discovery: ${err}`); + } + + // Return early if we're only running attack discovery graphs + return response.ok({ + body: { evaluationId, success: true }, + }); + } + const graphs: Array<{ name: string; graph: DefaultAssistantGraph; diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts index 34f009e266515..0260c47b4bd29 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/utils.ts @@ -21,7 +21,7 @@ export const fetchLangSmithDataset = async ( logger: Logger, langSmithApiKey?: string ): Promise<Example[]> => { - if (datasetName === undefined || !isLangSmithEnabled()) { + if (datasetName === undefined || (langSmithApiKey == null && !isLangSmithEnabled())) { throw new Error('LangSmith dataset name not provided or LangSmith not enabled'); } diff --git a/x-pack/plugins/elastic_assistant/server/routes/index.ts b/x-pack/plugins/elastic_assistant/server/routes/index.ts index 43e1229250f46..a6d7a4298c2b7 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/index.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/index.ts @@ -9,8 +9,8 @@ export { postActionsConnectorExecuteRoute } from './post_actions_connector_execute'; // Attack Discovery -export { postAttackDiscoveryRoute } from './attack_discovery/post_attack_discovery'; -export { getAttackDiscoveryRoute } from './attack_discovery/get_attack_discovery'; +export { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery'; +export { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery'; // Knowledge Base export { deleteKnowledgeBaseRoute } from './knowledge_base/delete_knowledge_base'; diff --git a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts index 56eb9760e442a..7898629e15b5c 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts @@ -7,9 +7,9 @@ import type { Logger } from '@kbn/core/server'; -import { cancelAttackDiscoveryRoute } from './attack_discovery/cancel_attack_discovery'; -import { getAttackDiscoveryRoute } from './attack_discovery/get_attack_discovery'; -import { postAttackDiscoveryRoute } from './attack_discovery/post_attack_discovery'; +import { cancelAttackDiscoveryRoute } from './attack_discovery/post/cancel/cancel_attack_discovery'; +import { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery'; +import { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery'; import { ElasticAssistantPluginRouter, GetElser } from '../types'; import { createConversationRoute } from './user_conversations/create_route'; import { deleteConversationRoute } from './user_conversations/delete_route'; diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index 45bd5a4149b58..e84b97ab43d7a 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -43,10 +43,10 @@ import { ActionsClientGeminiChatModel, ActionsClientLlm, } from '@kbn/langchain/server'; - import type { InferenceServerStart } from '@kbn/inference-plugin/server'; + import type { GetAIAssistantKnowledgeBaseDataClientParams } from './ai_assistant_data_clients/knowledge_base'; -import { AttackDiscoveryDataClient } from './ai_assistant_data_clients/attack_discovery'; +import { AttackDiscoveryDataClient } from './lib/attack_discovery/persistence'; import { AIAssistantConversationsDataClient } from './ai_assistant_data_clients/conversations'; import type { GetRegisteredFeatures, GetRegisteredTools } from './services/app_context'; import { AIAssistantDataClient } from './ai_assistant_data_clients'; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx index 885ab18c879a7..dd995d115b6c3 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/actionable_summary/index.tsx @@ -6,7 +6,11 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import { + replaceAnonymizedValuesWithOriginalValues, + type AttackDiscovery, + type Replacements, +} from '@kbn/elastic-assistant-common'; import React, { useMemo } from 'react'; import { AttackDiscoveryMarkdownFormatter } from '../../attack_discovery_markdown_formatter'; @@ -23,26 +27,41 @@ const ActionableSummaryComponent: React.FC<Props> = ({ replacements, showAnonymized = false, }) => { - const entitySummaryMarkdownWithReplacements = useMemo( + const entitySummary = useMemo( () => - Object.entries(replacements ?? {}).reduce( - (acc, [key, value]) => acc.replace(key, value), - attackDiscovery.entitySummaryMarkdown - ), - [attackDiscovery.entitySummaryMarkdown, replacements] + showAnonymized + ? attackDiscovery.entitySummaryMarkdown + : replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.entitySummaryMarkdown ?? '', + replacements: { ...replacements }, + }), + + [attackDiscovery.entitySummaryMarkdown, replacements, showAnonymized] + ); + + // title will be used as a fallback if entitySummaryMarkdown is empty + const title = useMemo( + () => + showAnonymized + ? attackDiscovery.title + : replaceAnonymizedValuesWithOriginalValues({ + messageContent: attackDiscovery.title, + replacements: { ...replacements }, + }), + + [attackDiscovery.title, replacements, showAnonymized] ); + const entitySummaryOrTitle = + entitySummary != null && entitySummary.length > 0 ? entitySummary : title; + return ( <EuiPanel color="subdued" data-test-subj="actionableSummary"> <EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween"> <EuiFlexItem data-test-subj="entitySummaryMarkdown" grow={false}> <AttackDiscoveryMarkdownFormatter disableActions={showAnonymized} - markdown={ - showAnonymized - ? attackDiscovery.entitySummaryMarkdown - : entitySummaryMarkdownWithReplacements - } + markdown={entitySummaryOrTitle} /> </EuiFlexItem> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx index 2aaac0449886a..c6ac9c70e8413 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/index.tsx @@ -49,8 +49,15 @@ const AttackDiscoveryPanelComponent: React.FC<Props> = ({ ); const buttonContent = useMemo( - () => <Title isLoading={false} title={attackDiscovery.title} />, - [attackDiscovery.title] + () => ( + <Title + isLoading={false} + replacements={replacements} + showAnonymized={showAnonymized} + title={attackDiscovery.title} + /> + ), + [attackDiscovery.title, replacements, showAnonymized] ); return ( diff --git a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx index 4b0375e4fe503..13326a07adc70 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/attack_discovery_panel/title/index.tsx @@ -7,20 +7,41 @@ import { EuiFlexGroup, EuiFlexItem, EuiSkeletonTitle, EuiTitle, useEuiTheme } from '@elastic/eui'; import { AssistantAvatar } from '@kbn/elastic-assistant'; +import { + replaceAnonymizedValuesWithOriginalValues, + type Replacements, +} from '@kbn/elastic-assistant-common'; import { css } from '@emotion/react'; -import React from 'react'; +import React, { useMemo } from 'react'; const AVATAR_SIZE = 24; // px interface Props { isLoading: boolean; + replacements?: Replacements; + showAnonymized?: boolean; title: string; } -const TitleComponent: React.FC<Props> = ({ isLoading, title }) => { +const TitleComponent: React.FC<Props> = ({ + isLoading, + replacements, + showAnonymized = false, + title, +}) => { const { euiTheme } = useEuiTheme(); + const titleWithReplacements = useMemo( + () => + replaceAnonymizedValuesWithOriginalValues({ + messageContent: title, + replacements: { ...replacements }, + }), + + [replacements, title] + ); + return ( <EuiFlexGroup alignItems="center" data-test-subj="title" gutterSize="s"> <EuiFlexItem @@ -53,7 +74,7 @@ const TitleComponent: React.FC<Props> = ({ isLoading, title }) => { /> ) : ( <EuiTitle data-test-subj="titleText" size="xs"> - <h2>{title}</h2> + <h2>{showAnonymized ? title : titleWithReplacements}</h2> </EuiTitle> )} </EuiFlexItem> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts b/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts index 5309ef1de6bb2..0ae524c25ee95 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/get_attack_discovery_markdown/get_attack_discovery_markdown.ts @@ -56,7 +56,7 @@ export const getAttackDiscoveryMarkdown = ({ replacements?: Replacements; }): string => { const title = getMarkdownFields(attackDiscovery.title); - const entitySummaryMarkdown = getMarkdownFields(attackDiscovery.entitySummaryMarkdown); + const entitySummaryMarkdown = getMarkdownFields(attackDiscovery.entitySummaryMarkdown ?? ''); const summaryMarkdown = getMarkdownFields(attackDiscovery.summaryMarkdown); const detailsMarkdown = getMarkdownFields(attackDiscovery.detailsMarkdown); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx b/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx index 874a4d1c99ded..ab0a5ac4ede96 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/hooks/use_poll_api.tsx @@ -106,7 +106,9 @@ export const usePollApi = ({ ...attackDiscovery, id: attackDiscovery.id ?? uuid.v4(), detailsMarkdown: replaceNewlineLiterals(attackDiscovery.detailsMarkdown), - entitySummaryMarkdown: replaceNewlineLiterals(attackDiscovery.entitySummaryMarkdown), + entitySummaryMarkdown: replaceNewlineLiterals( + attackDiscovery.entitySummaryMarkdown ?? '' + ), summaryMarkdown: replaceNewlineLiterals(attackDiscovery.summaryMarkdown), })), }; @@ -123,7 +125,7 @@ export const usePollApi = ({ const rawResponse = await http.fetch( `/internal/elastic_assistant/attack_discovery/cancel/${connectorId}`, { - method: 'PUT', + method: 'POST', version: ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION, } ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx index 5dd4cb8fc4267..533b95bf7087f 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/animated_counter/index.tsx @@ -52,7 +52,7 @@ const AnimatedCounterComponent: React.FC<Props> = ({ animationDurationMs = 1000 css={css` height: 32px; margin-right: ${euiTheme.size.xs}; - width: ${count < 100 ? 40 : 53}px; + width: ${count < 100 ? 40 : 60}px; `} data-test-subj="animatedCounter" ref={d3Ref} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx index 56b2205b28726..0707950383046 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.test.tsx @@ -16,6 +16,8 @@ jest.mock('../../../assistant/use_assistant_availability'); describe('EmptyPrompt', () => { const alertsCount = 20; + const aiConnectorsCount = 2; + const attackDiscoveriesCount = 0; const onGenerate = jest.fn(); beforeEach(() => { @@ -33,6 +35,8 @@ describe('EmptyPrompt', () => { <TestProviders> <EmptyPrompt alertsCount={alertsCount} + aiConnectorsCount={aiConnectorsCount} + attackDiscoveriesCount={attackDiscoveriesCount} isLoading={false} isDisabled={false} onGenerate={onGenerate} @@ -69,8 +73,34 @@ describe('EmptyPrompt', () => { }); describe('when loading is true', () => { - const isLoading = true; + beforeEach(() => { + (useAssistantAvailability as jest.Mock).mockReturnValue({ + hasAssistantPrivilege: true, + isAssistantEnabled: true, + }); + + render( + <TestProviders> + <EmptyPrompt + aiConnectorsCount={2} // <-- non-null + attackDiscoveriesCount={0} // <-- no discoveries + alertsCount={alertsCount} + isLoading={true} // <-- loading + isDisabled={false} + onGenerate={onGenerate} + /> + </TestProviders> + ); + }); + + it('returns null', () => { + const emptyPrompt = screen.queryByTestId('emptyPrompt'); + expect(emptyPrompt).not.toBeInTheDocument(); + }); + }); + + describe('when aiConnectorsCount is null', () => { beforeEach(() => { (useAssistantAvailability as jest.Mock).mockReturnValue({ hasAssistantPrivilege: true, @@ -80,8 +110,10 @@ describe('EmptyPrompt', () => { render( <TestProviders> <EmptyPrompt + aiConnectorsCount={null} // <-- null + attackDiscoveriesCount={0} // <-- no discoveries alertsCount={alertsCount} - isLoading={isLoading} + isLoading={false} // <-- not loading isDisabled={false} onGenerate={onGenerate} /> @@ -89,10 +121,38 @@ describe('EmptyPrompt', () => { ); }); - it('disables the generate button while loading', () => { - const generateButton = screen.getByTestId('generate'); + it('returns null', () => { + const emptyPrompt = screen.queryByTestId('emptyPrompt'); - expect(generateButton).toBeDisabled(); + expect(emptyPrompt).not.toBeInTheDocument(); + }); + }); + + describe('when there are attack discoveries', () => { + beforeEach(() => { + (useAssistantAvailability as jest.Mock).mockReturnValue({ + hasAssistantPrivilege: true, + isAssistantEnabled: true, + }); + + render( + <TestProviders> + <EmptyPrompt + aiConnectorsCount={2} // <-- non-null + attackDiscoveriesCount={7} // there are discoveries + alertsCount={alertsCount} + isLoading={false} // <-- not loading + isDisabled={false} + onGenerate={onGenerate} + /> + </TestProviders> + ); + }); + + it('returns null', () => { + const emptyPrompt = screen.queryByTestId('emptyPrompt'); + + expect(emptyPrompt).not.toBeInTheDocument(); }); }); @@ -109,6 +169,8 @@ describe('EmptyPrompt', () => { <TestProviders> <EmptyPrompt alertsCount={alertsCount} + aiConnectorsCount={2} // <-- non-null + attackDiscoveriesCount={0} // <-- no discoveries isLoading={false} isDisabled={isDisabled} onGenerate={onGenerate} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx index 75c8533efcc92..3d89f5be87030 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_prompt/index.tsx @@ -7,7 +7,6 @@ import { AssistantAvatar } from '@kbn/elastic-assistant'; import { - EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, @@ -15,24 +14,28 @@ import { EuiLink, EuiSpacer, EuiText, - EuiToolTip, useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; import React, { useMemo } from 'react'; import { AnimatedCounter } from './animated_counter'; +import { Generate } from '../generate'; import * as i18n from './translations'; interface Props { + aiConnectorsCount: number | null; // null when connectors are not configured alertsCount: number; + attackDiscoveriesCount: number; isDisabled?: boolean; isLoading: boolean; onGenerate: () => void; } const EmptyPromptComponent: React.FC<Props> = ({ + aiConnectorsCount, alertsCount, + attackDiscoveriesCount, isLoading, isDisabled = false, onGenerate, @@ -110,25 +113,13 @@ const EmptyPromptComponent: React.FC<Props> = ({ ); const actions = useMemo(() => { - const disabled = isLoading || isDisabled; - - return ( - <EuiToolTip - content={disabled ? i18n.SELECT_A_CONNECTOR : null} - data-test-subj="generateTooltip" - > - <EuiButton - color="primary" - data-test-subj="generate" - disabled={disabled} - onClick={onGenerate} - > - {i18n.GENERATE} - </EuiButton> - </EuiToolTip> - ); + return <Generate isLoading={isLoading} isDisabled={isDisabled} onGenerate={onGenerate} />; }, [isDisabled, isLoading, onGenerate]); + if (isLoading || aiConnectorsCount == null || attackDiscoveriesCount > 0) { + return null; + } + return ( <EuiFlexGroup alignItems="center" diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts new file mode 100644 index 0000000000000..e2c7018ef5826 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/helpers/show_empty_states/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + showEmptyPrompt, + showFailurePrompt, + showNoAlertsPrompt, + showWelcomePrompt, +} from '../../../helpers'; + +export const showEmptyStates = ({ + aiConnectorsCount, + alertsContextCount, + attackDiscoveriesCount, + connectorId, + failureReason, + isLoading, +}: { + aiConnectorsCount: number | null; + alertsContextCount: number | null; + attackDiscoveriesCount: number; + connectorId: string | undefined; + failureReason: string | null; + isLoading: boolean; +}): boolean => { + const showWelcome = showWelcomePrompt({ aiConnectorsCount, isLoading }); + const showFailure = showFailurePrompt({ connectorId, failureReason, isLoading }); + const showNoAlerts = showNoAlertsPrompt({ alertsContextCount, connectorId, isLoading }); + const showEmpty = showEmptyPrompt({ aiConnectorsCount, attackDiscoveriesCount, isLoading }); + + return showWelcome || showFailure || showNoAlerts || showEmpty; +}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx index 3b5b87ada83ec..9eacd696a2ff1 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { render, screen } from '@testing-library/react'; import React from 'react'; @@ -18,7 +19,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = 0; // <-- no connectors configured const alertsContextCount = null; - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = false; @@ -29,12 +29,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -59,7 +59,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 0; // <-- no alerts to analyze - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; @@ -70,12 +69,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -104,8 +103,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 10; - const alertsCount = 10; - const attackDiscoveriesCount = 10; + const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -115,12 +113,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={"you're a failure"} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -149,8 +147,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 10; - const alertsCount = 10; - const attackDiscoveriesCount = 10; + const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const failureReason = 'this failure should NOT be displayed, because we are loading'; // <-- failureReason is provided const isLoading = true; // <-- loading data @@ -161,12 +158,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={failureReason} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -195,8 +192,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const alertsCount = 0; // <-- no alerts contributed to attack discoveries - const attackDiscoveriesCount = 0; // <-- no attack discoveries were generated from the alerts + const attackDiscoveriesCount = 0; const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -206,12 +202,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -240,7 +236,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = null; // <-- no connectors configured const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = false; @@ -251,12 +246,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -287,7 +282,6 @@ describe('EmptyStates', () => { const aiConnectorsCount = 0; // <-- no connectors configured (welcome prompt should be shown if not loading) const alertsContextCount = null; - const alertsCount = 0; const attackDiscoveriesCount = 0; const connectorId = undefined; const isLoading = true; // <-- loading data @@ -298,12 +292,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); @@ -338,8 +332,7 @@ describe('EmptyStates', () => { const aiConnectorsCount = 1; const alertsContextCount = 20; // <-- alerts were sent as context to be analyzed - const alertsCount = 10; // <-- alerts contributed to attack discoveries - const attackDiscoveriesCount = 3; // <-- attack discoveries were generated from the alerts + const attackDiscoveriesCount = 7; // <-- attack discoveries are present const connectorId = 'test-connector-id'; const isLoading = false; const onGenerate = jest.fn(); @@ -349,12 +342,12 @@ describe('EmptyStates', () => { <EmptyStates aiConnectorsCount={aiConnectorsCount} alertsContextCount={alertsContextCount} - alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} connectorId={connectorId} failureReason={null} isLoading={isLoading} onGenerate={onGenerate} + upToAlertsCount={DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx index 49b4557c72192..a083ec7b77fdd 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/empty_states/index.tsx @@ -9,51 +9,55 @@ import React from 'react'; import { Failure } from '../failure'; import { EmptyPrompt } from '../empty_prompt'; -import { showEmptyPrompt, showNoAlertsPrompt, showWelcomePrompt } from '../helpers'; +import { showFailurePrompt, showNoAlertsPrompt, showWelcomePrompt } from '../helpers'; import { NoAlerts } from '../no_alerts'; import { Welcome } from '../welcome'; interface Props { - aiConnectorsCount: number | null; - alertsContextCount: number | null; - alertsCount: number; + aiConnectorsCount: number | null; // null when connectors are not configured + alertsContextCount: number | null; // null when unavailable for the current connector attackDiscoveriesCount: number; connectorId: string | undefined; failureReason: string | null; isLoading: boolean; onGenerate: () => Promise<void>; + upToAlertsCount: number; } const EmptyStatesComponent: React.FC<Props> = ({ aiConnectorsCount, alertsContextCount, - alertsCount, attackDiscoveriesCount, connectorId, failureReason, isLoading, onGenerate, + upToAlertsCount, }) => { + const isDisabled = connectorId == null; + if (showWelcomePrompt({ aiConnectorsCount, isLoading })) { return <Welcome />; - } else if (!isLoading && failureReason != null) { + } + + if (showFailurePrompt({ connectorId, failureReason, isLoading })) { return <Failure failureReason={failureReason} />; - } else if (showNoAlertsPrompt({ alertsContextCount, isLoading })) { - return <NoAlerts />; - } else if (showEmptyPrompt({ aiConnectorsCount, attackDiscoveriesCount, isLoading })) { - return ( - <EmptyPrompt - alertsCount={alertsCount} - isDisabled={connectorId == null} - isLoading={isLoading} - onGenerate={onGenerate} - /> - ); } - return null; -}; + if (showNoAlertsPrompt({ alertsContextCount, connectorId, isLoading })) { + return <NoAlerts isLoading={isLoading} isDisabled={isDisabled} onGenerate={onGenerate} />; + } -EmptyStatesComponent.displayName = 'EmptyStates'; + return ( + <EmptyPrompt + aiConnectorsCount={aiConnectorsCount} + alertsCount={upToAlertsCount} + attackDiscoveriesCount={attackDiscoveriesCount} + isDisabled={isDisabled} + isLoading={isLoading} + onGenerate={onGenerate} + /> + ); +}; export const EmptyStates = React.memo(EmptyStatesComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx index 4318f3f78536a..c9c27446fe51c 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/index.tsx @@ -5,13 +5,53 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; +import { + EuiAccordion, + EuiCodeBlock, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiText, +} from '@elastic/eui'; import { css } from '@emotion/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import * as i18n from './translations'; -const FailureComponent: React.FC<{ failureReason: string }> = ({ failureReason }) => { +interface Props { + failureReason: string | null | undefined; +} + +const FailureComponent: React.FC<Props> = ({ failureReason }) => { + const Failures = useMemo(() => { + const failures = failureReason != null ? failureReason.split('\n') : ''; + const [firstFailure, ...restFailures] = failures; + + return ( + <> + <p>{firstFailure}</p> + + {restFailures.length > 0 && ( + <EuiAccordion + id="failuresFccordion" + buttonContent={i18n.DETAILS} + data-test-subj="failuresAccordion" + paddingSize="s" + > + <> + {restFailures.map((failure, i) => ( + <EuiCodeBlock fontSize="m" key={i} paddingSize="m"> + {failure} + </EuiCodeBlock> + ))} + </> + </EuiAccordion> + )} + </> + ); + }, [failureReason]); + return ( <EuiFlexGroup alignItems="center" data-test-subj="failure" direction="column"> <EuiFlexItem data-test-subj="emptyPromptContainer" grow={false}> @@ -26,7 +66,7 @@ const FailureComponent: React.FC<{ failureReason: string }> = ({ failureReason } `} data-test-subj="bodyText" > - {failureReason} + {Failures} </EuiText> } title={<h2 data-test-subj="failureTitle">{i18n.FAILURE_TITLE}</h2>} diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts index b36104d202ba8..ecaa7fad240e1 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/failure/translations.ts @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; -export const LEARN_MORE = i18n.translate( - 'xpack.securitySolution.attackDiscovery.pages.failure.learnMoreLink', +export const DETAILS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.pages.failure.detailsAccordionButton', { - defaultMessage: 'Learn more about Attack discovery', + defaultMessage: 'Details', } ); @@ -20,3 +20,10 @@ export const FAILURE_TITLE = i18n.translate( defaultMessage: 'Attack discovery generation failed', } ); + +export const LEARN_MORE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.pages.failure.learnMoreLink', + { + defaultMessage: 'Learn more about Attack discovery', + } +); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx new file mode 100644 index 0000000000000..16ed376dd3af4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/generate/index.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton, EuiToolTip } from '@elastic/eui'; +import React from 'react'; + +import * as i18n from '../empty_prompt/translations'; + +interface Props { + isDisabled?: boolean; + isLoading: boolean; + onGenerate: () => void; +} + +const GenerateComponent: React.FC<Props> = ({ isLoading, isDisabled = false, onGenerate }) => { + const disabled = isLoading || isDisabled; + + return ( + <EuiToolTip + content={disabled ? i18n.SELECT_A_CONNECTOR : null} + data-test-subj="generateTooltip" + > + <EuiButton color="primary" data-test-subj="generate" disabled={disabled} onClick={onGenerate}> + {i18n.GENERATE} + </EuiButton> + </EuiToolTip> + ); +}; + +GenerateComponent.displayName = 'Generate'; + +export const Generate = React.memo(GenerateComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx index aee53d889c7ac..7b0688eadafef 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; @@ -31,9 +32,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onGenerate={jest.fn()} onConnectorIdSelected={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -54,9 +57,11 @@ describe('Header', () => { connectorsAreConfigured={connectorsAreConfigured} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onGenerate={jest.fn()} onConnectorIdSelected={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -77,9 +82,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={onGenerate} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -102,9 +109,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={isLoading} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -126,9 +135,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={isLoading} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={onCancel} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); @@ -150,9 +161,11 @@ describe('Header', () => { connectorsAreConfigured={true} isDisabledActions={false} isLoading={false} + localStorageAttackDiscoveryMaxAlerts={`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`} onCancel={jest.fn()} onConnectorIdSelected={jest.fn()} onGenerate={jest.fn()} + setLocalStorageAttackDiscoveryMaxAlerts={jest.fn()} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx index 583bcc25d0eb6..ff170805670a6 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/index.tsx @@ -9,10 +9,11 @@ import type { EuiButtonProps } from '@elastic/eui'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { ConnectorSelectorInline } from '@kbn/elastic-assistant'; +import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; +import { SettingsModal } from './settings_modal'; import { StatusBell } from './status_bell'; import * as i18n from './translations'; @@ -21,9 +22,11 @@ interface Props { connectorsAreConfigured: boolean; isLoading: boolean; isDisabledActions: boolean; + localStorageAttackDiscoveryMaxAlerts: string | undefined; onGenerate: () => void; onCancel: () => void; onConnectorIdSelected: (connectorId: string) => void; + setLocalStorageAttackDiscoveryMaxAlerts: React.Dispatch<React.SetStateAction<string | undefined>>; stats: AttackDiscoveryStats | null; } @@ -32,9 +35,11 @@ const HeaderComponent: React.FC<Props> = ({ connectorsAreConfigured, isLoading, isDisabledActions, + localStorageAttackDiscoveryMaxAlerts, onGenerate, onConnectorIdSelected, onCancel, + setLocalStorageAttackDiscoveryMaxAlerts, stats, }) => { const { euiTheme } = useEuiTheme(); @@ -68,6 +73,7 @@ const HeaderComponent: React.FC<Props> = ({ }, [isLoading, handleCancel, onGenerate] ); + return ( <EuiFlexGroup alignItems="center" @@ -78,6 +84,14 @@ const HeaderComponent: React.FC<Props> = ({ data-test-subj="header" gutterSize="none" > + <EuiFlexItem grow={false}> + <SettingsModal + connectorId={connectorId} + isLoading={isLoading} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + setLocalStorageAttackDiscoveryMaxAlerts={setLocalStorageAttackDiscoveryMaxAlerts} + /> + </EuiFlexItem> <StatusBell stats={stats} /> {connectorsAreConfigured && ( <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx new file mode 100644 index 0000000000000..b51a1fc3f85c8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/alerts_settings/index.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SingleRangeChangeEvent } from '@kbn/elastic-assistant'; +import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; +import { + AlertsRange, + SELECT_FEWER_ALERTS, + YOUR_ANONYMIZATION_SETTINGS, +} from '@kbn/elastic-assistant'; +import React, { useCallback } from 'react'; + +import * as i18n from '../translations'; + +export const MAX_ALERTS = 500; +export const MIN_ALERTS = 50; +export const ROW_MIN_WITH = 550; // px +export const STEP = 50; + +interface Props { + maxAlerts: string; + setMaxAlerts: React.Dispatch<React.SetStateAction<string>>; +} + +const AlertsSettingsComponent: React.FC<Props> = ({ maxAlerts, setMaxAlerts }) => { + const onChangeAlertsRange = useCallback( + (e: SingleRangeChangeEvent) => { + setMaxAlerts(e.currentTarget.value); + }, + [setMaxAlerts] + ); + + return ( + <EuiForm component="form"> + <EuiFormRow hasChildLabel={false} label={i18n.ALERTS}> + <EuiFlexGroup direction="column" gutterSize="none"> + <EuiFlexItem grow={false}> + <AlertsRange + maxAlerts={MAX_ALERTS} + minAlerts={MIN_ALERTS} + onChange={onChangeAlertsRange} + step={STEP} + value={maxAlerts} + /> + <EuiSpacer size="m" /> + </EuiFlexItem> + + <EuiFlexItem grow={true}> + <EuiText color="subdued" size="xs"> + <span>{i18n.LATEST_AND_RISKIEST_OPEN_ALERTS(Number(maxAlerts))}</span> + </EuiText> + </EuiFlexItem> + + <EuiFlexItem grow={true}> + <EuiText color="subdued" size="xs"> + <span>{YOUR_ANONYMIZATION_SETTINGS}</span> + </EuiText> + </EuiFlexItem> + + <EuiFlexItem grow={true}> + <EuiText color="subdued" size="xs"> + <span>{SELECT_FEWER_ALERTS}</span> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + </EuiForm> + ); +}; + +AlertsSettingsComponent.displayName = 'AlertsSettings'; + +export const AlertsSettings = React.memo(AlertsSettingsComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx new file mode 100644 index 0000000000000..0066376a0e198 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/footer/index.tsx @@ -0,0 +1,57 @@ +/* + * 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 { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; + +import * as i18n from '../translations'; + +interface Props { + closeModal: () => void; + onReset: () => void; + onSave: () => void; +} + +const FooterComponent: React.FC<Props> = ({ closeModal, onReset, onSave }) => { + const { euiTheme } = useEuiTheme(); + + return ( + <EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty data-test-sub="reset" flush="both" onClick={onReset} size="s"> + {i18n.RESET} + </EuiButtonEmpty> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiFlexGroup alignItems="center" gutterSize="none"> + <EuiFlexItem + css={css` + margin-right: ${euiTheme.size.s}; + `} + grow={false} + > + <EuiButtonEmpty data-test-sub="cancel" onClick={closeModal} size="s"> + {i18n.CANCEL} + </EuiButtonEmpty> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiButton data-test-sub="save" fill onClick={onSave} size="s"> + {i18n.SAVE} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +FooterComponent.displayName = 'Footer'; + +export const Footer = React.memo(FooterComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx new file mode 100644 index 0000000000000..7543985c74786 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/index.tsx @@ -0,0 +1,160 @@ +/* + * 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 { + EuiButtonIcon, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, + EuiToolTip, + EuiTourStep, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { + ATTACK_DISCOVERY_STORAGE_KEY, + DEFAULT_ASSISTANT_NAMESPACE, + DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY, +} from '@kbn/elastic-assistant'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; + +import { AlertsSettings } from './alerts_settings'; +import { useSpaceId } from '../../../../common/hooks/use_space_id'; +import { Footer } from './footer'; +import { getIsTourEnabled } from './is_tour_enabled'; +import * as i18n from './translations'; + +interface Props { + connectorId: string | undefined; + isLoading: boolean; + localStorageAttackDiscoveryMaxAlerts: string | undefined; + setLocalStorageAttackDiscoveryMaxAlerts: React.Dispatch<React.SetStateAction<string | undefined>>; +} + +const SettingsModalComponent: React.FC<Props> = ({ + connectorId, + isLoading, + localStorageAttackDiscoveryMaxAlerts, + setLocalStorageAttackDiscoveryMaxAlerts, +}) => { + const spaceId = useSpaceId() ?? 'default'; + const modalTitleId = useGeneratedHtmlId(); + + const [maxAlerts, setMaxAlerts] = useState( + localStorageAttackDiscoveryMaxAlerts ?? `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}` + ); + + const [isModalVisible, setIsModalVisible] = useState(false); + const showModal = useCallback(() => { + setMaxAlerts(localStorageAttackDiscoveryMaxAlerts ?? `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`); + + setIsModalVisible(true); + }, [localStorageAttackDiscoveryMaxAlerts]); + const closeModal = useCallback(() => setIsModalVisible(false), []); + + const onReset = useCallback(() => setMaxAlerts(`${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}`), []); + + const onSave = useCallback(() => { + setLocalStorageAttackDiscoveryMaxAlerts(maxAlerts); + closeModal(); + }, [closeModal, maxAlerts, setLocalStorageAttackDiscoveryMaxAlerts]); + + const [showSettingsTour, setShowSettingsTour] = useLocalStorage<boolean>( + `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${SHOW_SETTINGS_TOUR_LOCAL_STORAGE_KEY}.v8.16`, + true + ); + const onTourFinished = useCallback(() => setShowSettingsTour(() => false), [setShowSettingsTour]); + const [tourDelayElapsed, setTourDelayElapsed] = useState(false); + + useEffect(() => { + // visible EuiTourStep anchors don't follow the button when the layout changes (i.e. when the connectors finish loading) + const timeout = setTimeout(() => setTourDelayElapsed(true), 10000); + return () => clearTimeout(timeout); + }, []); + + const onSettingsClicked = useCallback(() => { + showModal(); + setShowSettingsTour(() => false); + }, [setShowSettingsTour, showModal]); + + const SettingsButton = useMemo( + () => ( + <EuiToolTip content={i18n.SETTINGS}> + <EuiButtonIcon + aria-label={i18n.SETTINGS} + data-test-subj="settings" + iconType="gear" + onClick={onSettingsClicked} + /> + </EuiToolTip> + ), + [onSettingsClicked] + ); + + const isTourEnabled = getIsTourEnabled({ + connectorId, + isLoading, + tourDelayElapsed, + showSettingsTour, + }); + + return ( + <> + {isTourEnabled ? ( + <EuiTourStep + anchorPosition="downCenter" + content={ + <> + <EuiText size="s"> + <p> + <span>{i18n.ATTACK_DISCOVERY_SENDS_MORE_ALERTS}</span> + <br /> + <span>{i18n.CONFIGURE_YOUR_SETTINGS_HERE}</span> + </p> + </EuiText> + </> + } + isStepOpen={showSettingsTour} + minWidth={300} + onFinish={onTourFinished} + step={1} + stepsTotal={1} + subtitle={i18n.RECENT_ATTACK_DISCOVERY_IMPROVEMENTS} + title={i18n.SEND_MORE_ALERTS} + > + {SettingsButton} + </EuiTourStep> + ) : ( + <>{SettingsButton}</> + )} + + {isModalVisible && ( + <EuiModal aria-labelledby={modalTitleId} data-test-subj="modal" onClose={closeModal}> + <EuiModalHeader> + <EuiModalHeaderTitle id={modalTitleId}>{i18n.SETTINGS}</EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <AlertsSettings maxAlerts={maxAlerts} setMaxAlerts={setMaxAlerts} /> + </EuiModalBody> + + <EuiModalFooter> + <Footer closeModal={closeModal} onReset={onReset} onSave={onSave} /> + </EuiModalFooter> + </EuiModal> + )} + </> + ); +}; + +SettingsModalComponent.displayName = 'SettingsModal'; + +export const SettingsModal = React.memo(SettingsModalComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts new file mode 100644 index 0000000000000..7f2f356114902 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/is_tour_enabled/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getIsTourEnabled = ({ + connectorId, + isLoading, + tourDelayElapsed, + showSettingsTour, +}: { + connectorId: string | undefined; + isLoading: boolean; + tourDelayElapsed: boolean; + showSettingsTour: boolean | undefined; +}): boolean => !isLoading && connectorId != null && tourDelayElapsed && !!showSettingsTour; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts new file mode 100644 index 0000000000000..dc42db84f2d8a --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/header/settings_modal/translations.ts @@ -0,0 +1,81 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ALERTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.alertsLabel', + { + defaultMessage: 'Alerts', + } +); + +export const ATTACK_DISCOVERY_SENDS_MORE_ALERTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.attackDiscoverySendsMoreAlertsTourText', + { + defaultMessage: 'Attack discovery sends more alerts as context.', + } +); + +export const CANCEL = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.cancelButton', + { + defaultMessage: 'Cancel', + } +); + +export const CONFIGURE_YOUR_SETTINGS_HERE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.configureYourSettingsHereTourText', + { + defaultMessage: 'Configure your settings here.', + } +); + +export const LATEST_AND_RISKIEST_OPEN_ALERTS = (alertsCount: number) => + i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.latestAndRiskiestOpenAlertsLabel', + { + defaultMessage: + 'Send Attack discovery information about your {alertsCount} newest and riskiest open or acknowledged alerts.', + values: { alertsCount }, + } + ); + +export const SAVE = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.saveButton', + { + defaultMessage: 'Save', + } +); + +export const SEND_MORE_ALERTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.tourTitle', + { + defaultMessage: 'Send more alerts', + } +); + +export const SETTINGS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.settingsLabel', + { + defaultMessage: 'Settings', + } +); + +export const RECENT_ATTACK_DISCOVERY_IMPROVEMENTS = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.tourSubtitle', + { + defaultMessage: 'Recent Attack discovery improvements', + } +); + +export const RESET = i18n.translate( + 'xpack.securitySolution.attackDiscovery.settingsModal.resetLabel', + { + defaultMessage: 'Reset', + } +); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts index e94687611ea8f..c7e1c579418b4 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.test.ts @@ -12,6 +12,7 @@ describe('helpers', () => { it('returns true when isLoading is false and alertsContextCount is 0', () => { const result = showNoAlertsPrompt({ alertsContextCount: 0, + connectorId: 'test', isLoading: false, }); @@ -21,6 +22,7 @@ describe('helpers', () => { it('returns false when isLoading is true', () => { const result = showNoAlertsPrompt({ alertsContextCount: 0, + connectorId: 'test', isLoading: true, }); @@ -30,6 +32,7 @@ describe('helpers', () => { it('returns false when alertsContextCount is null', () => { const result = showNoAlertsPrompt({ alertsContextCount: null, + connectorId: 'test', isLoading: false, }); @@ -39,6 +42,7 @@ describe('helpers', () => { it('returns false when alertsContextCount greater than 0', () => { const result = showNoAlertsPrompt({ alertsContextCount: 20, + connectorId: 'test', isLoading: false, }); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts index e3d3be963bacd..b990c3ccf1555 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/helpers.ts @@ -75,11 +75,14 @@ export const getErrorToastText = ( export const showNoAlertsPrompt = ({ alertsContextCount, + connectorId, isLoading, }: { alertsContextCount: number | null; + connectorId: string | undefined; isLoading: boolean; -}): boolean => !isLoading && alertsContextCount != null && alertsContextCount === 0; +}): boolean => + connectorId != null && !isLoading && alertsContextCount != null && alertsContextCount === 0; export const showWelcomePrompt = ({ aiConnectorsCount, @@ -111,12 +114,26 @@ export const showLoading = ({ loadingConnectorId: string | null; }): boolean => isLoading && (loadingConnectorId === connectorId || attackDiscoveriesCount === 0); -export const showSummary = ({ +export const showSummary = (attackDiscoveriesCount: number) => attackDiscoveriesCount > 0; + +export const showFailurePrompt = ({ connectorId, - attackDiscoveriesCount, - loadingConnectorId, + failureReason, + isLoading, }: { connectorId: string | undefined; - attackDiscoveriesCount: number; - loadingConnectorId: string | null; -}): boolean => loadingConnectorId !== connectorId && attackDiscoveriesCount > 0; + failureReason: string | null; + isLoading: boolean; +}): boolean => connectorId != null && !isLoading && failureReason != null; + +export const getSize = ({ + defaultMaxAlerts, + localStorageAttackDiscoveryMaxAlerts, +}: { + defaultMaxAlerts: number; + localStorageAttackDiscoveryMaxAlerts: string | undefined; +}): number => { + const size = Number(localStorageAttackDiscoveryMaxAlerts); + + return isNaN(size) || size <= 0 ? defaultMaxAlerts : size; +}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx index ea5c16fc3cbba..e55b2fe5083b6 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/index.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; import { css } from '@emotion/react'; import { ATTACK_DISCOVERY_STORAGE_KEY, DEFAULT_ASSISTANT_NAMESPACE, + DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + MAX_ALERTS_LOCAL_STORAGE_KEY, useAssistantContext, useLoadConnectors, } from '@kbn/elastic-assistant'; @@ -23,23 +25,16 @@ import { HeaderPage } from '../../common/components/header_page'; import { useSpaceId } from '../../common/hooks/use_space_id'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { Header } from './header'; -import { - CONNECTOR_ID_LOCAL_STORAGE_KEY, - getInitialIsOpen, - showLoading, - showSummary, -} from './helpers'; -import { AttackDiscoveryPanel } from '../attack_discovery_panel'; -import { EmptyStates } from './empty_states'; +import { CONNECTOR_ID_LOCAL_STORAGE_KEY, getSize, showLoading } from './helpers'; import { LoadingCallout } from './loading_callout'; import { PageTitle } from './page_title'; -import { Summary } from './summary'; +import { Results } from './results'; import { useAttackDiscovery } from '../use_attack_discovery'; const AttackDiscoveryPageComponent: React.FC = () => { const spaceId = useSpaceId() ?? 'default'; - const { http, knowledgeBase } = useAssistantContext(); + const { http } = useAssistantContext(); const { data: aiConnectors } = useLoadConnectors({ http, }); @@ -54,6 +49,12 @@ const AttackDiscoveryPageComponent: React.FC = () => { `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${CONNECTOR_ID_LOCAL_STORAGE_KEY}` ); + const [localStorageAttackDiscoveryMaxAlerts, setLocalStorageAttackDiscoveryMaxAlerts] = + useLocalStorage<string>( + `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${MAX_ALERTS_LOCAL_STORAGE_KEY}`, + `${DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS}` + ); + const [connectorId, setConnectorId] = React.useState<string | undefined>( localStorageAttackDiscoveryConnectorId ); @@ -78,6 +79,10 @@ const AttackDiscoveryPageComponent: React.FC = () => { } = useAttackDiscovery({ connectorId, setLoadingConnectorId, + size: getSize({ + defaultMaxAlerts: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + localStorageAttackDiscoveryMaxAlerts, + }), }); // get last updated from the cached attack discoveries if it exists: @@ -159,9 +164,11 @@ const AttackDiscoveryPageComponent: React.FC = () => { isLoading={isLoading} // disable header actions before post request has completed isDisabledActions={isLoadingPost} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} onConnectorIdSelected={onConnectorIdSelected} onGenerate={onGenerate} onCancel={onCancel} + setLocalStorageAttackDiscoveryMaxAlerts={setLocalStorageAttackDiscoveryMaxAlerts} stats={stats} /> <EuiSpacer size="m" /> @@ -170,68 +177,37 @@ const AttackDiscoveryPageComponent: React.FC = () => { <EuiEmptyPrompt data-test-subj="animatedLogo" icon={animatedLogo} /> ) : ( <> - {showSummary({ + {showLoading({ attackDiscoveriesCount, connectorId, + isLoading: isLoading || isLoadingPost, loadingConnectorId, - }) && ( - <Summary + }) ? ( + <LoadingCallout + alertsContextCount={alertsContextCount} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + approximateFutureTime={approximateFutureTime} + connectorIntervals={connectorIntervals} + /> + ) : ( + <Results + aiConnectorsCount={aiConnectors?.length ?? null} + alertsContextCount={alertsContextCount} alertsCount={alertsCount} attackDiscoveriesCount={attackDiscoveriesCount} - lastUpdated={selectedConnectorLastUpdated} + connectorId={connectorId} + failureReason={failureReason} + isLoading={isLoading} + isLoadingPost={isLoadingPost} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + onGenerate={onGenerate} onToggleShowAnonymized={onToggleShowAnonymized} + selectedConnectorAttackDiscoveries={selectedConnectorAttackDiscoveries} + selectedConnectorLastUpdated={selectedConnectorLastUpdated} + selectedConnectorReplacements={selectedConnectorReplacements} showAnonymized={showAnonymized} /> )} - - <> - {showLoading({ - attackDiscoveriesCount, - connectorId, - isLoading: isLoading || isLoadingPost, - loadingConnectorId, - }) ? ( - <LoadingCallout - alertsCount={knowledgeBase.latestAlerts} - approximateFutureTime={approximateFutureTime} - connectorIntervals={connectorIntervals} - /> - ) : ( - selectedConnectorAttackDiscoveries.map((attackDiscovery, i) => ( - <React.Fragment key={attackDiscovery.id}> - <AttackDiscoveryPanel - attackDiscovery={attackDiscovery} - initialIsOpen={getInitialIsOpen(i)} - showAnonymized={showAnonymized} - replacements={selectedConnectorReplacements} - /> - <EuiSpacer size="l" /> - </React.Fragment> - )) - )} - </> - <EuiFlexGroup - css={css` - max-height: 100%; - min-height: 100%; - `} - direction="column" - gutterSize="none" - > - <EuiSpacer size="xxl" /> - <EuiFlexItem grow={false}> - <EmptyStates - aiConnectorsCount={aiConnectors?.length ?? null} - alertsContextCount={alertsContextCount} - alertsCount={knowledgeBase.latestAlerts} - attackDiscoveriesCount={attackDiscoveriesCount} - failureReason={failureReason} - connectorId={connectorId} - isLoading={isLoading || isLoadingPost} - onGenerate={onGenerate} - /> - </EuiFlexItem> - </EuiFlexGroup> </> )} <SpyRoute pageName={SecurityPageName.attackDiscovery} /> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx index af6efafb3c1dd..f755017288300 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.test.tsx @@ -29,9 +29,10 @@ describe('LoadingCallout', () => { ]; const defaultProps = { - alertsCount: 30, + alertsContextCount: 30, approximateFutureTime: new Date(), connectorIntervals, + localStorageAttackDiscoveryMaxAlerts: '50', }; it('renders the animated loading icon', () => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx index 7e392e3165711..aee8241ec73fc 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/index.tsx @@ -20,13 +20,15 @@ const BACKGROUND_COLOR_DARK = '#0B2030'; const BORDER_COLOR_DARK = '#0B2030'; interface Props { - alertsCount: number; + alertsContextCount: number | null; approximateFutureTime: Date | null; connectorIntervals: GenerationInterval[]; + localStorageAttackDiscoveryMaxAlerts: string | undefined; } const LoadingCalloutComponent: React.FC<Props> = ({ - alertsCount, + alertsContextCount, + localStorageAttackDiscoveryMaxAlerts, approximateFutureTime, connectorIntervals, }) => { @@ -46,11 +48,14 @@ const LoadingCalloutComponent: React.FC<Props> = ({ `} grow={false} > - <LoadingMessages alertsCount={alertsCount} /> + <LoadingMessages + alertsContextCount={alertsContextCount} + localStorageAttackDiscoveryMaxAlerts={localStorageAttackDiscoveryMaxAlerts} + /> </EuiFlexItem> </EuiFlexGroup> ), - [alertsCount, euiTheme.size.m] + [alertsContextCount, euiTheme.size.m, localStorageAttackDiscoveryMaxAlerts] ); const isDarkMode = theme.getTheme().darkMode === true; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts new file mode 100644 index 0000000000000..9a3061272ca15 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/get_loading_callout_alerts_count/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getLoadingCalloutAlertsCount = ({ + alertsContextCount, + defaultMaxAlerts, + localStorageAttackDiscoveryMaxAlerts, +}: { + alertsContextCount: number | null; + defaultMaxAlerts: number; + localStorageAttackDiscoveryMaxAlerts: string | undefined; +}): number => { + if (alertsContextCount != null && !isNaN(alertsContextCount) && alertsContextCount > 0) { + return alertsContextCount; + } + + const size = Number(localStorageAttackDiscoveryMaxAlerts); + + return isNaN(size) || size <= 0 ? defaultMaxAlerts : size; +}; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx index 250a25055791a..8b3f174792c5e 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.test.tsx @@ -16,7 +16,7 @@ describe('LoadingMessages', () => { it('renders the expected loading message', () => { render( <TestProviders> - <LoadingMessages alertsCount={20} /> + <LoadingMessages alertsContextCount={20} localStorageAttackDiscoveryMaxAlerts={'30'} /> </TestProviders> ); const attackDiscoveryGenerationInProgress = screen.getByTestId( @@ -31,7 +31,7 @@ describe('LoadingMessages', () => { it('renders the loading message with the expected alerts count', () => { render( <TestProviders> - <LoadingMessages alertsCount={20} /> + <LoadingMessages alertsContextCount={20} localStorageAttackDiscoveryMaxAlerts={'30'} /> </TestProviders> ); const aiCurrentlyAnalyzing = screen.getByTestId('aisCurrentlyAnalyzing'); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx index 9acd7b4d2dbbf..1a84771e5c635 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/loading_callout/loading_messages/index.tsx @@ -7,22 +7,34 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import React from 'react'; import { useKibana } from '../../../../common/lib/kibana'; +import { getLoadingCalloutAlertsCount } from './get_loading_callout_alerts_count'; import * as i18n from '../translations'; const TEXT_COLOR = '#343741'; interface Props { - alertsCount: number; + alertsContextCount: number | null; + localStorageAttackDiscoveryMaxAlerts: string | undefined; } -const LoadingMessagesComponent: React.FC<Props> = ({ alertsCount }) => { +const LoadingMessagesComponent: React.FC<Props> = ({ + alertsContextCount, + localStorageAttackDiscoveryMaxAlerts, +}) => { const { theme } = useKibana().services; const isDarkMode = theme.getTheme().darkMode === true; + const alertsCount = getLoadingCalloutAlertsCount({ + alertsContextCount, + defaultMaxAlerts: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, + localStorageAttackDiscoveryMaxAlerts, + }); + return ( <EuiFlexGroup data-test-subj="loadingMessages" direction="column" gutterSize="none"> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx index 6c2640623e370..6c6bbfb25cb7f 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.test.tsx @@ -13,7 +13,7 @@ import { ATTACK_DISCOVERY_ONLY, LEARN_MORE, NO_ALERTS_TO_ANALYZE } from './trans describe('NoAlerts', () => { beforeEach(() => { - render(<NoAlerts />); + render(<NoAlerts isDisabled={false} isLoading={false} onGenerate={jest.fn()} />); }); it('renders the avatar', () => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx index a7b0cd929336b..ace75f568bf3d 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/no_alerts/index.tsx @@ -17,8 +17,15 @@ import { import React, { useMemo } from 'react'; import * as i18n from './translations'; +import { Generate } from '../generate'; -const NoAlertsComponent: React.FC = () => { +interface Props { + isDisabled: boolean; + isLoading: boolean; + onGenerate: () => void; +} + +const NoAlertsComponent: React.FC<Props> = ({ isDisabled, isLoading, onGenerate }) => { const title = useMemo( () => ( <EuiFlexGroup @@ -83,6 +90,14 @@ const NoAlertsComponent: React.FC = () => { {i18n.LEARN_MORE} </EuiLink> </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiSpacer size="m" /> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <Generate isDisabled={isDisabled} isLoading={isLoading} onGenerate={onGenerate} /> + </EuiFlexItem> </EuiFlexGroup> ); }; diff --git a/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx new file mode 100644 index 0000000000000..6e3e43127e711 --- /dev/null +++ b/x-pack/plugins/security_solution/public/attack_discovery/pages/results/index.tsx @@ -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 { EuiSpacer } from '@elastic/eui'; +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; +import type { AttackDiscovery, Replacements } from '@kbn/elastic-assistant-common'; +import React from 'react'; + +import { AttackDiscoveryPanel } from '../../attack_discovery_panel'; +import { EmptyStates } from '../empty_states'; +import { showEmptyStates } from '../empty_states/helpers/show_empty_states'; +import { getInitialIsOpen, showSummary } from '../helpers'; +import { Summary } from '../summary'; + +interface Props { + aiConnectorsCount: number | null; // null when connectors are not configured + alertsContextCount: number | null; // null when unavailable for the current connector + alertsCount: number; + attackDiscoveriesCount: number; + connectorId: string | undefined; + failureReason: string | null; + isLoading: boolean; + isLoadingPost: boolean; + localStorageAttackDiscoveryMaxAlerts: string | undefined; + onGenerate: () => Promise<void>; + onToggleShowAnonymized: () => void; + selectedConnectorAttackDiscoveries: AttackDiscovery[]; + selectedConnectorLastUpdated: Date | null; + selectedConnectorReplacements: Replacements; + showAnonymized: boolean; +} + +const ResultsComponent: React.FC<Props> = ({ + aiConnectorsCount, + alertsContextCount, + alertsCount, + attackDiscoveriesCount, + connectorId, + failureReason, + isLoading, + isLoadingPost, + localStorageAttackDiscoveryMaxAlerts, + onGenerate, + onToggleShowAnonymized, + selectedConnectorAttackDiscoveries, + selectedConnectorLastUpdated, + selectedConnectorReplacements, + showAnonymized, +}) => { + if ( + showEmptyStates({ + aiConnectorsCount, + alertsContextCount, + attackDiscoveriesCount, + connectorId, + failureReason, + isLoading, + }) + ) { + return ( + <> + <EuiSpacer size="xxl" /> + <EmptyStates + aiConnectorsCount={aiConnectorsCount} + alertsContextCount={alertsContextCount} + attackDiscoveriesCount={attackDiscoveriesCount} + failureReason={failureReason} + connectorId={connectorId} + isLoading={isLoading || isLoadingPost} + onGenerate={onGenerate} + upToAlertsCount={Number( + localStorageAttackDiscoveryMaxAlerts ?? DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS + )} + /> + </> + ); + } + + return ( + <> + {showSummary(attackDiscoveriesCount) && ( + <Summary + alertsCount={alertsCount} + attackDiscoveriesCount={attackDiscoveriesCount} + lastUpdated={selectedConnectorLastUpdated} + onToggleShowAnonymized={onToggleShowAnonymized} + showAnonymized={showAnonymized} + /> + )} + + {selectedConnectorAttackDiscoveries.map((attackDiscovery, i) => ( + <React.Fragment key={attackDiscovery.id}> + <AttackDiscoveryPanel + attackDiscovery={attackDiscovery} + initialIsOpen={getInitialIsOpen(i)} + showAnonymized={showAnonymized} + replacements={selectedConnectorReplacements} + /> + <EuiSpacer size="l" /> + </React.Fragment> + ))} + </> + ); +}; + +ResultsComponent.displayName = 'Results'; + +export const Results = React.memo(ResultsComponent); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts index f2fd17d5978b7..cc0034c90d1fa 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS } from '@kbn/elastic-assistant'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { omit } from 'lodash/fp'; @@ -132,9 +133,7 @@ describe('getRequestBody', () => { }, ], }; - const knowledgeBase = { - latestAlerts: 20, - }; + const traceOptions = { apmUrl: '/app/apm', langSmithProject: '', @@ -145,7 +144,7 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern, anonymizationFields, - knowledgeBase, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -160,8 +159,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, replacements: {}, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); @@ -170,7 +169,7 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern: undefined, anonymizationFields, - knowledgeBase, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -185,8 +184,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, replacements: {}, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); @@ -195,7 +194,7 @@ describe('getRequestBody', () => { const withLangSmith = { alertsIndexPattern, anonymizationFields, - knowledgeBase, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions: { apmUrl: '/app/apm', langSmithProject: 'A project', @@ -216,7 +215,7 @@ describe('getRequestBody', () => { }, langSmithApiKey: withLangSmith.traceOptions.langSmithApiKey, langSmithProject: withLangSmith.traceOptions.langSmithProject, - size: knowledgeBase.latestAlerts, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, replacements: {}, subAction: 'invokeAI', }); @@ -226,8 +225,8 @@ describe('getRequestBody', () => { const result = getRequestBody({ alertsIndexPattern, anonymizationFields, - knowledgeBase, selectedConnector: connector, // <-- selectedConnector is provided + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -242,7 +241,7 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, replacements: {}, subAction: 'invokeAI', }); @@ -258,8 +257,8 @@ describe('getRequestBody', () => { alertsIndexPattern, anonymizationFields, genAiConfig, // <-- genAiConfig is provided - knowledgeBase, selectedConnector: connector, // <-- selectedConnector is provided + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, traceOptions, }); @@ -274,8 +273,8 @@ describe('getRequestBody', () => { }, langSmithProject: undefined, langSmithApiKey: undefined, - size: knowledgeBase.latestAlerts, replacements: {}, + size: DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS, subAction: 'invokeAI', }); }); diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts index 97eb132bdaaeb..7aa9bfdd118d9 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/helpers.ts @@ -5,10 +5,7 @@ * 2.0. */ -import type { - KnowledgeBaseConfig, - TraceOptions, -} from '@kbn/elastic-assistant/impl/assistant/types'; +import type { TraceOptions } from '@kbn/elastic-assistant/impl/assistant/types'; import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import type { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; @@ -60,8 +57,8 @@ export const getRequestBody = ({ alertsIndexPattern, anonymizationFields, genAiConfig, - knowledgeBase, selectedConnector, + size, traceOptions, }: { alertsIndexPattern: string | undefined; @@ -83,7 +80,7 @@ export const getRequestBody = ({ }>; }; genAiConfig?: GenAiConfig; - knowledgeBase: KnowledgeBaseConfig; + size: number; selectedConnector?: ActionConnector; traceOptions: TraceOptions; }): AttackDiscoveryPostRequestBody => ({ @@ -95,8 +92,8 @@ export const getRequestBody = ({ langSmithApiKey: isEmpty(traceOptions?.langSmithApiKey) ? undefined : traceOptions?.langSmithApiKey, - size: knowledgeBase.latestAlerts, replacements: {}, // no need to re-use replacements in the current implementation + size, subAction: 'invokeAI', // non-streaming apiConfig: { connectorId: selectedConnector?.id ?? '', diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx index 6329ce5ca699a..59659ee6d8649 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.test.tsx @@ -106,6 +106,8 @@ const mockAttackDiscoveries = [ const setLoadingConnectorId = jest.fn(); const setStatus = jest.fn(); +const SIZE = 20; + describe('useAttackDiscovery', () => { const mockPollApi = { cancelAttackDiscovery: jest.fn(), @@ -126,7 +128,11 @@ describe('useAttackDiscovery', () => { it('initializes with correct default values', () => { const { result } = renderHook(() => - useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId }) + useAttackDiscovery({ + connectorId: 'test-id', + setLoadingConnectorId, + size: 20, + }) ); expect(result.current.alertsContextCount).toBeNull(); @@ -144,14 +150,15 @@ describe('useAttackDiscovery', () => { it('fetches attack discoveries and updates state correctly', async () => { (mockedUseKibana.services.http.fetch as jest.Mock).mockResolvedValue(mockAttackDiscoveryPost); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); + await act(async () => { await result.current.fetchAttackDiscoveries(); }); expect(mockedUseKibana.services.http.fetch).toHaveBeenCalledWith( '/internal/elastic_assistant/attack_discovery', { - body: '{"alertsIndexPattern":"alerts-index-pattern","anonymizationFields":[],"size":20,"replacements":{},"subAction":"invokeAI","apiConfig":{"connectorId":"test-id","actionTypeId":".gen-ai"}}', + body: `{"alertsIndexPattern":"alerts-index-pattern","anonymizationFields":[],"replacements":{},"size":${SIZE},"subAction":"invokeAI","apiConfig":{"connectorId":"test-id","actionTypeId":".gen-ai"}}`, method: 'POST', version: '1', } @@ -167,7 +174,7 @@ describe('useAttackDiscovery', () => { const error = new Error(errorMessage); (mockedUseKibana.services.http.fetch as jest.Mock).mockRejectedValue(error); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); await act(async () => { await result.current.fetchAttackDiscoveries(); @@ -184,7 +191,11 @@ describe('useAttackDiscovery', () => { it('sets loading state based on poll status', async () => { (usePollApi as jest.Mock).mockReturnValue({ ...mockPollApi, status: 'running' }); const { result } = renderHook(() => - useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId }) + useAttackDiscovery({ + connectorId: 'test-id', + setLoadingConnectorId, + size: SIZE, + }) ); expect(result.current.isLoading).toBe(true); @@ -202,7 +213,7 @@ describe('useAttackDiscovery', () => { }, status: 'succeeded', }); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); expect(result.current.alertsContextCount).toEqual(20); // this is set from usePollApi @@ -227,7 +238,7 @@ describe('useAttackDiscovery', () => { }, status: 'failed', }); - const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id' })); + const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE })); expect(result.current.failureReason).toEqual('something bad'); expect(result.current.isLoading).toBe(false); @@ -241,7 +252,13 @@ describe('useAttackDiscovery', () => { data: [], // <-- zero connectors configured }); - renderHook(() => useAttackDiscovery({ connectorId: 'test-id', setLoadingConnectorId })); + renderHook(() => + useAttackDiscovery({ + connectorId: 'test-id', + setLoadingConnectorId, + size: SIZE, + }) + ); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx index deb1c556bdb43..4ad78981d4540 100644 --- a/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx +++ b/x-pack/plugins/security_solution/public/attack_discovery/use_attack_discovery/index.tsx @@ -43,9 +43,11 @@ export interface UseAttackDiscovery { export const useAttackDiscovery = ({ connectorId, + size, setLoadingConnectorId, }: { connectorId: string | undefined; + size: number; setLoadingConnectorId?: (loadingConnectorId: string | null) => void; }): UseAttackDiscovery => { // get Kibana services and connectors @@ -75,7 +77,7 @@ export const useAttackDiscovery = ({ const [isLoading, setIsLoading] = useState(false); // get alerts index pattern and allow lists from the assistant context: - const { alertsIndexPattern, knowledgeBase, traceOptions } = useAssistantContext(); + const { alertsIndexPattern, traceOptions } = useAssistantContext(); const { data: anonymizationFields } = useFetchAnonymizationFields(); @@ -95,18 +97,11 @@ export const useAttackDiscovery = ({ alertsIndexPattern, anonymizationFields, genAiConfig, - knowledgeBase, + size, selectedConnector, traceOptions, }); - }, [ - aiConnectors, - alertsIndexPattern, - anonymizationFields, - connectorId, - knowledgeBase, - traceOptions, - ]); + }, [aiConnectors, alertsIndexPattern, anonymizationFields, connectorId, size, traceOptions]); useEffect(() => { if ( @@ -140,7 +135,7 @@ export const useAttackDiscovery = ({ useEffect(() => { if (pollData !== null && pollData.connectorId === connectorId) { if (pollData.alertsContextCount != null) setAlertsContextCount(pollData.alertsContextCount); - if (pollData.attackDiscoveries.length) { + if (pollData.attackDiscoveries.length && pollData.attackDiscoveries[0].timestamp != null) { // get last updated from timestamp, not from updatedAt since this can indicate the last time the status was updated setLastUpdated(new Date(pollData.attackDiscoveries[0].timestamp)); } diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts deleted file mode 100644 index 4d06751f57d7d..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-common'; -import type { ActionsClientLlm } from '@kbn/langchain/server'; -import type { DynamicTool } from '@langchain/core/tools'; - -import { loggerMock } from '@kbn/logging-mocks'; - -import { ATTACK_DISCOVERY_TOOL } from './attack_discovery_tool'; -import { mockAnonymizationFields } from '../mock/mock_anonymization_fields'; -import { mockEmptyOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_empty_open_and_acknowledged_alerts_qery_results'; -import { mockOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_open_and_acknowledged_alerts_query_results'; - -jest.mock('langchain/chains', () => { - const mockLLMChain = jest.fn().mockImplementation(() => ({ - call: jest.fn().mockResolvedValue({ - records: [ - { - alertIds: [ - 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', - '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', - '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', - 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', - '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', - ], - detailsMarkdown: - '- Malicious Go application named "My Go Application.app" is being executed from temporary directories, likely indicating malware delivery\n- The malicious application is spawning child processes like `osascript` to display fake system dialogs and attempt to phish user credentials ({{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }}, {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }})\n- The malicious application is also executing `chmod` to make the file `unix1` executable ({{ file.path /Users/james/unix1 }})\n- `unix1` is a potentially malicious executable that is being run with suspicious arguments related to the macOS keychain ({{ process.command_line /Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!! }})\n- Multiple detections indicate the presence of malware on the host attempting credential access and execution of malicious payloads', - entitySummaryMarkdown: - 'Malicious activity detected on {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} involving user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - mitreAttackTactics: ['Credential Access', 'Execution'], - summaryMarkdown: - 'Multiple detections indicate the presence of malware on a macOS host {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} attempting credential theft and execution of malicious payloads targeting the user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - title: 'Malware Delivering Malicious Payloads on macOS', - }, - ], - }), - })); - - return { - LLMChain: mockLLMChain, - }; -}); - -describe('AttackDiscoveryTool', () => { - const alertsIndexPattern = '.alerts-security.alerts-default'; - const replacements = { uuid: 'original_value' }; - const size = 20; - const request = { - body: { - actionTypeId: '.bedrock', - alertsIndexPattern, - anonymizationFields: mockAnonymizationFields, - connectorId: 'test-connector-id', - replacements, - size, - subAction: 'invokeAI', - }, - } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; - - const esClient = { - search: jest.fn(), - } as unknown as ElasticsearchClient; - const llm = jest.fn() as unknown as ActionsClientLlm; - const logger = loggerMock.create(); - - const rest = { - anonymizationFields: mockAnonymizationFields, - isEnabledKnowledgeBase: false, - llm, - logger, - onNewReplacements: jest.fn(), - size, - }; - - beforeEach(() => { - jest.clearAllMocks(); - - (esClient.search as jest.Mock).mockResolvedValue(mockOpenAndAcknowledgedAlertsQueryResults); - }); - - describe('isSupported', () => { - it('returns false when the request is missing required anonymization parameters', () => { - const requestMissingAnonymizationParams = { - body: { - isEnabledKnowledgeBase: false, - alertsIndexPattern: '.alerts-security.alerts-default', - size: 20, - }, - } as unknown as KibanaRequest<unknown, unknown, AttackDiscoveryPostRequestBody>; - - const params = { - alertsIndexPattern, - esClient, - request: requestMissingAnonymizationParams, // <-- request is missing required anonymization parameters - ...rest, - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when the alertsIndexPattern is undefined', () => { - const params = { - esClient, - request, - ...rest, - alertsIndexPattern: undefined, // <-- alertsIndexPattern is undefined - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when size is undefined', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - size: undefined, // <-- size is undefined - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when size is out of range', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - size: 0, // <-- size is out of range - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false when llm is undefined', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - llm: undefined, // <-- llm is undefined - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(false); - }); - - it('returns true if all required params are provided', () => { - const params = { - alertsIndexPattern, - esClient, - request, - ...rest, - }; - - expect(ATTACK_DISCOVERY_TOOL.isSupported(params)).toBe(true); - }); - }); - - describe('getTool', () => { - it('returns null when llm is undefined', () => { - const tool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - llm: undefined, // <-- llm is undefined - }); - - expect(tool).toBeNull(); - }); - - it('returns a `DynamicTool` with a `func` that calls `esClient.search()` with the expected alerts query', async () => { - const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - await tool.func(''); - - expect(esClient.search).toHaveBeenCalledWith({ - allow_no_indices: true, - body: { - _source: false, - fields: mockAnonymizationFields.map(({ field }) => ({ - field, - include_unmapped: true, - })), - query: { - bool: { - filter: [ - { - bool: { - filter: [ - { - bool: { - minimum_should_match: 1, - should: [ - { - match_phrase: { - 'kibana.alert.workflow_status': 'open', - }, - }, - { - match_phrase: { - 'kibana.alert.workflow_status': 'acknowledged', - }, - }, - ], - }, - }, - { - range: { - '@timestamp': { - format: 'strict_date_optional_time', - gte: 'now-24h', - lte: 'now', - }, - }, - }, - ], - must: [], - must_not: [ - { - exists: { - field: 'kibana.alert.building_block_type', - }, - }, - ], - should: [], - }, - }, - ], - }, - }, - runtime_mappings: {}, - size, - sort: [ - { - 'kibana.alert.risk_score': { - order: 'desc', - }, - }, - { - '@timestamp': { - order: 'desc', - }, - }, - ], - }, - ignore_unavailable: true, - index: [alertsIndexPattern], - }); - }); - - it('returns a `DynamicTool` with a `func` returns an empty attack discoveries array when getAnonymizedAlerts returns no alerts', async () => { - (esClient.search as jest.Mock).mockResolvedValue( - mockEmptyOpenAndAcknowledgedAlertsQueryResults // <-- no alerts - ); - - const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - const result = await tool.func(''); - const expected = JSON.stringify({ alertsContextCount: 0, attackDiscoveries: [] }, null, 2); // <-- empty attack discoveries array - - expect(result).toEqual(expected); - }); - - it('returns a `DynamicTool` with a `func` that returns the expected results', async () => { - const tool: DynamicTool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - await tool.func(''); - - const result = await tool.func(''); - const expected = JSON.stringify( - { - alertsContextCount: 20, - attackDiscoveries: [ - { - alertIds: [ - 'b6e883c29b32571aaa667fa13e65bbb4f95172a2b84bdfb85d6f16c72b2d2560', - '0215a6c5cc9499dd0290cd69a4947efb87d3ddd8b6385a766d122c2475be7367', - '600eb9eca925f4c5b544b4e9d3cf95d83b7829f8f74c5bd746369cb4c2968b9a', - 'e1f4a4ed70190eb4bd256c813029a6a9101575887cdbfa226ac330fbd3063f0c', - '2a7a4809ca625dfe22ccd35fbef7a7ba8ed07f109e5cbd17250755cfb0bc615f', - ], - detailsMarkdown: - '- Malicious Go application named "My Go Application.app" is being executed from temporary directories, likely indicating malware delivery\n- The malicious application is spawning child processes like `osascript` to display fake system dialogs and attempt to phish user credentials ({{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }}, {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }})\n- The malicious application is also executing `chmod` to make the file `unix1` executable ({{ file.path /Users/james/unix1 }})\n- `unix1` is a potentially malicious executable that is being run with suspicious arguments related to the macOS keychain ({{ process.command_line /Users/james/unix1 /Users/james/library/Keychains/login.keychain-db TempTemp1234!! }})\n- Multiple detections indicate the presence of malware on the host attempting credential access and execution of malicious payloads', - entitySummaryMarkdown: - 'Malicious activity detected on {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} involving user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - mitreAttackTactics: ['Credential Access', 'Execution'], - summaryMarkdown: - 'Multiple detections indicate the presence of malware on a macOS host {{ host.name 6c57a4f7-b30b-465d-a670-47377655b1bb }} attempting credential theft and execution of malicious payloads targeting the user {{ user.name 639fab6d-369b-4879-beae-7767a7145c7f }}.', - title: 'Malware Delivering Malicious Payloads on macOS', - }, - ], - }, - null, - 2 - ); - - expect(result).toEqual(expected); - }); - - it('returns a tool instance with the expected tags', () => { - const tool = ATTACK_DISCOVERY_TOOL.getTool({ - alertsIndexPattern, - esClient, - replacements, - request, - ...rest, - }) as DynamicTool; - - expect(tool.tags).toEqual(['attack-discovery']); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts deleted file mode 100644 index 264862d76b8f5..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { PromptTemplate } from '@langchain/core/prompts'; -import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; -import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; -import { LLMChain } from 'langchain/chains'; -import { OutputFixingParser } from 'langchain/output_parsers'; -import { DynamicTool } from '@langchain/core/tools'; - -import { APP_UI_ID } from '../../../../common'; -import { getAnonymizedAlerts } from './get_anonymized_alerts'; -import { getOutputParser } from './get_output_parser'; -import { sizeIsOutOfRange } from '../open_and_acknowledged_alerts/helpers'; -import { getAttackDiscoveryPrompt } from './get_attack_discovery_prompt'; - -export interface AttackDiscoveryToolParams extends AssistantToolParams { - alertsIndexPattern: string; - size: number; -} - -export const ATTACK_DISCOVERY_TOOL_DESCRIPTION = - 'Call this for attack discoveries containing `markdown` that should be displayed verbatim (with no additional processing).'; - -/** - * Returns a tool for generating attack discoveries from open and acknowledged - * alerts, or null if the request doesn't have all the required parameters. - */ -export const ATTACK_DISCOVERY_TOOL: AssistantTool = { - id: 'attack-discovery', - name: 'AttackDiscoveryTool', - description: ATTACK_DISCOVERY_TOOL_DESCRIPTION, - sourceRegister: APP_UI_ID, - isSupported: (params: AssistantToolParams): params is AttackDiscoveryToolParams => { - const { alertsIndexPattern, llm, request, size } = params; - - return ( - requestHasRequiredAnonymizationParams(request) && - alertsIndexPattern != null && - size != null && - !sizeIsOutOfRange(size) && - llm != null - ); - }, - getTool(params: AssistantToolParams) { - if (!this.isSupported(params)) return null; - - const { - alertsIndexPattern, - anonymizationFields, - esClient, - langChainTimeout, - llm, - onNewReplacements, - replacements, - size, - } = params as AttackDiscoveryToolParams; - - return new DynamicTool({ - name: 'AttackDiscoveryTool', - description: ATTACK_DISCOVERY_TOOL_DESCRIPTION, - func: async () => { - if (llm == null) { - throw new Error('LLM is required for attack discoveries'); - } - - const anonymizedAlerts = await getAnonymizedAlerts({ - alertsIndexPattern, - anonymizationFields, - esClient, - onNewReplacements, - replacements, - size, - }); - - const alertsContextCount = anonymizedAlerts.length; - if (alertsContextCount === 0) { - // No alerts to analyze, so return an empty attack discoveries array - return JSON.stringify({ alertsContextCount, attackDiscoveries: [] }, null, 2); - } - - const outputParser = getOutputParser(); - const outputFixingParser = OutputFixingParser.fromLLM(llm, outputParser); - - const prompt = new PromptTemplate({ - template: `Answer the user's question as best you can:\n{format_instructions}\n{query}`, - inputVariables: ['query'], - partialVariables: { - format_instructions: outputFixingParser.getFormatInstructions(), - }, - }); - - const answerFormattingChain = new LLMChain({ - llm, - prompt, - outputKey: 'records', - outputParser: outputFixingParser, - }); - - const result = await answerFormattingChain.call({ - query: getAttackDiscoveryPrompt({ anonymizedAlerts }), - timeout: langChainTimeout, - }); - const attackDiscoveries = result.records; - - return JSON.stringify({ alertsContextCount, attackDiscoveries }, null, 2); - }, - tags: ['attack-discovery'], - }); - }, -}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts deleted file mode 100644 index df211f0bd0a7d..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_attack_discovery_prompt.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// NOTE: we ask the LLM to `provide insights`. We do NOT use the feature name, `AttackDiscovery`, in the prompt. -export const getAttackDiscoveryPrompt = ({ - anonymizedAlerts, -}: { - anonymizedAlerts: string[]; -}) => `You are a cyber security analyst tasked with analyzing security events from Elastic Security to identify and report on potential cyber attacks or progressions. Your report should focus on high-risk incidents that could severely impact the organization, rather than isolated alerts. Present your findings in a way that can be easily understood by anyone, regardless of their technical expertise, as if you were briefing the CISO. Break down your response into sections based on timing, hosts, and users involved. When correlating alerts, use kibana.alert.original_time when it's available, otherwise use @timestamp. Include appropriate context about the affected hosts and users. Describe how the attack progression might have occurred and, if feasible, attribute it to known threat groups. Prioritize high and critical alerts, but include lower-severity alerts if desired. In the description field, provide as much detail as possible, in a bulleted list explaining any attack progressions. Accuracy is of utmost importance. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. - -Use context from the following open and acknowledged alerts to provide insights: - -""" -${anonymizedAlerts.join('\n\n')} -""" -`; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts deleted file mode 100644 index 5ad2cd11f817a..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { getOutputParser } from './get_output_parser'; - -describe('getOutputParser', () => { - it('returns a structured output parser with the expected format instructions', () => { - const outputParser = getOutputParser(); - - const expected = `You must format your output as a JSON value that adheres to a given \"JSON Schema\" instance. - -\"JSON Schema\" is a declarative language that allows you to annotate and validate JSON documents. - -For example, the example \"JSON Schema\" instance {{\"properties\": {{\"foo\": {{\"description\": \"a list of test words\", \"type\": \"array\", \"items\": {{\"type\": \"string\"}}}}}}, \"required\": [\"foo\"]}}}} -would match an object with one required property, \"foo\". The \"type\" property specifies \"foo\" must be an \"array\", and the \"description\" property semantically describes it as \"a list of test words\". The items within \"foo\" must be strings. -Thus, the object {{\"foo\": [\"bar\", \"baz\"]}} is a well-formatted instance of this example \"JSON Schema\". The object {{\"properties\": {{\"foo\": [\"bar\", \"baz\"]}}}} is not well-formatted. - -Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas! - -Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock: -\`\`\`json -{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"alertIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The alert IDs that the insight is based on.\"},\"detailsMarkdown\":{\"type\":\"string\",\"description\":\"A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\"},\"entitySummaryMarkdown\":{\"type\":\"string\",\"description\":\"A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"mitreAttackTactics\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"An array of MITRE ATT&CK tactic for the insight, using one of the following values: Reconnaissance,Initial Access,Execution,Persistence,Privilege Escalation,Discovery,Lateral Movement,Command and Control,Exfiltration\"},\"summaryMarkdown\":{\"type\":\"string\",\"description\":\"A markdown summary of insight, using the same {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax\"},\"title\":{\"type\":\"string\",\"description\":\"A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.\"}},\"required\":[\"alertIds\",\"detailsMarkdown\",\"summaryMarkdown\",\"title\"],\"additionalProperties\":false},\"description\":\"Insights with markdown that always uses special {{ field.name fieldValue1 fieldValue2 fieldValueN }} syntax for field names and values from the source data. Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }} Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}\",\"$schema\":\"http://json-schema.org/draft-07/schema#\"} -\`\`\` -`; - - expect(outputParser.getFormatInstructions()).toEqual(expected); - }); -}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts deleted file mode 100644 index 3d66257f060e4..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/get_output_parser.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { StructuredOutputParser } from 'langchain/output_parsers'; -import { z } from '@kbn/zod'; - -export const SYNTAX = '{{ field.name fieldValue1 fieldValue2 fieldValueN }}'; -const GOOD_SYNTAX_EXAMPLES = - 'Examples of CORRECT syntax (includes field names and values): {{ host.name hostNameValue }} {{ user.name userNameValue }} {{ source.ip sourceIpValue }}'; - -const BAD_SYNTAX_EXAMPLES = - 'Examples of INCORRECT syntax (bad, because the field names are not included): {{ hostNameValue }} {{ userNameValue }} {{ sourceIpValue }}'; - -const RECONNAISSANCE = 'Reconnaissance'; -const INITIAL_ACCESS = 'Initial Access'; -const EXECUTION = 'Execution'; -const PERSISTENCE = 'Persistence'; -const PRIVILEGE_ESCALATION = 'Privilege Escalation'; -const DISCOVERY = 'Discovery'; -const LATERAL_MOVEMENT = 'Lateral Movement'; -const COMMAND_AND_CONTROL = 'Command and Control'; -const EXFILTRATION = 'Exfiltration'; - -const MITRE_ATTACK_TACTICS = [ - RECONNAISSANCE, - INITIAL_ACCESS, - EXECUTION, - PERSISTENCE, - PRIVILEGE_ESCALATION, - DISCOVERY, - LATERAL_MOVEMENT, - COMMAND_AND_CONTROL, - EXFILTRATION, -] as const; - -// NOTE: we ask the LLM for `insight`s. We do NOT use the feature name, `AttackDiscovery`, in the prompt. -export const getOutputParser = () => - StructuredOutputParser.fromZodSchema( - z - .array( - z.object({ - alertIds: z.string().array().describe(`The alert IDs that the insight is based on.`), - detailsMarkdown: z - .string() - .describe( - `A detailed insight with markdown, where each markdown bullet contains a description of what happened that reads like a story of the attack as it played out and always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` - ), - entitySummaryMarkdown: z - .string() - .optional() - .describe( - `A short (no more than a sentence) summary of the insight featuring only the host.name and user.name fields (when they are applicable), using the same ${SYNTAX} syntax` - ), - mitreAttackTactics: z - .string() - .array() - .optional() - .describe( - `An array of MITRE ATT&CK tactic for the insight, using one of the following values: ${MITRE_ATTACK_TACTICS.join( - ',' - )}` - ), - summaryMarkdown: z - .string() - .describe(`A markdown summary of insight, using the same ${SYNTAX} syntax`), - title: z - .string() - .describe( - 'A short, no more than 7 words, title for the insight, NOT formatted with special syntax or markdown. This must be as brief as possible.' - ), - }) - ) - .describe( - `Insights with markdown that always uses special ${SYNTAX} syntax for field names and values from the source data. ${GOOD_SYNTAX_EXAMPLES} ${BAD_SYNTAX_EXAMPLES}` - ) - ); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/index.ts index a704aaa44d0a1..1b6e90eb7280f 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/index.ts @@ -10,7 +10,6 @@ import type { AssistantTool } from '@kbn/elastic-assistant-plugin/server'; import { NL_TO_ESQL_TOOL } from './esql/nl_to_esql_tool'; import { ALERT_COUNTS_TOOL } from './alert_counts/alert_counts_tool'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool'; -import { ATTACK_DISCOVERY_TOOL } from './attack_discovery/attack_discovery_tool'; import { KNOWLEDGE_BASE_RETRIEVAL_TOOL } from './knowledge_base/knowledge_base_retrieval_tool'; import { KNOWLEDGE_BASE_WRITE_TOOL } from './knowledge_base/knowledge_base_write_tool'; import { SECURITY_LABS_KNOWLEDGE_BASE_TOOL } from './security_labs/security_labs_tool'; @@ -22,7 +21,6 @@ export const getAssistantTools = ({ }): AssistantTool[] => { const tools = [ ALERT_COUNTS_TOOL, - ATTACK_DISCOVERY_TOOL, NL_TO_ESQL_TOOL, KNOWLEDGE_BASE_RETRIEVAL_TOOL, KNOWLEDGE_BASE_WRITE_TOOL, diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts deleted file mode 100644 index 722936a368b36..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - getRawDataOrDefault, - isRawDataValid, - MAX_SIZE, - MIN_SIZE, - sizeIsOutOfRange, -} from './helpers'; - -describe('helpers', () => { - describe('isRawDataValid', () => { - it('returns true for valid raw data', () => { - const rawData = { - field1: [1, 2, 3], // the Fields API may return a number array - field2: ['a', 'b', 'c'], // the Fields API may return a string array - }; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns true when a field array is empty', () => { - const rawData = { - field1: [1, 2, 3], // the Fields API may return a number array - field2: ['a', 'b', 'c'], // the Fields API may return a string array - field3: [], // the Fields API may return an empty array - }; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns false when a field does not have an array of values', () => { - const rawData = { - field1: [1, 2, 3], - field2: 'invalid', - }; - - expect(isRawDataValid(rawData)).toBe(false); - }); - - it('returns true for empty raw data', () => { - const rawData = {}; - - expect(isRawDataValid(rawData)).toBe(true); - }); - - it('returns false when raw data is an unexpected type', () => { - const rawData = 1234; - - // @ts-expect-error - expect(isRawDataValid(rawData)).toBe(false); - }); - }); - - describe('getRawDataOrDefault', () => { - it('returns the raw data when it is valid', () => { - const rawData = { - field1: [1, 2, 3], - field2: ['a', 'b', 'c'], - }; - - expect(getRawDataOrDefault(rawData)).toEqual(rawData); - }); - - it('returns an empty object when the raw data is invalid', () => { - const rawData = { - field1: [1, 2, 3], - field2: 'invalid', - }; - - expect(getRawDataOrDefault(rawData)).toEqual({}); - }); - }); - - describe('sizeIsOutOfRange', () => { - it('returns true when size is undefined', () => { - const size = undefined; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns true when size is less than MIN_SIZE', () => { - const size = MIN_SIZE - 1; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns true when size is greater than MAX_SIZE', () => { - const size = MAX_SIZE + 1; - - expect(sizeIsOutOfRange(size)).toBe(true); - }); - - it('returns false when size is exactly MIN_SIZE', () => { - const size = MIN_SIZE; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - - it('returns false when size is exactly MAX_SIZE', () => { - const size = MAX_SIZE; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - - it('returns false when size is within the valid range', () => { - const size = MIN_SIZE + 1; - - expect(sizeIsOutOfRange(size)).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts deleted file mode 100644 index dcb30e04e9dbc..0000000000000 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/helpers.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; - -export const MIN_SIZE = 10; -export const MAX_SIZE = 10000; - -export type MaybeRawData = SearchResponse['fields'] | undefined; // note: this is the type of the "fields" property in the ES response - -export const isRawDataValid = (rawData: MaybeRawData): rawData is Record<string, unknown[]> => - typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x])); - -export const getRawDataOrDefault = (rawData: MaybeRawData): Record<string, unknown[]> => - isRawDataValid(rawData) ? rawData : {}; - -export const sizeIsOutOfRange = (size?: number): boolean => - size == null || size < MIN_SIZE || size > MAX_SIZE; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts index 09bae1639f1b1..45587b65f5f4c 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts @@ -10,12 +10,13 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { DynamicTool } from '@langchain/core/tools'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts_tool'; -import { MAX_SIZE } from './helpers'; import type { RetrievalQAChain } from 'langchain/chains'; import { mockAlertsFieldsApi } from '@kbn/elastic-assistant-plugin/server/__mocks__/alerts'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; import { loggerMock } from '@kbn/logging-mocks'; +const MAX_SIZE = 10000; + describe('OpenAndAcknowledgedAlertsTool', () => { const alertsIndexPattern = 'alerts-index'; const esClient = { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts index d6b0ad58d8adb..cab015183f4a2 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts @@ -7,13 +7,17 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { Replacements } from '@kbn/elastic-assistant-common'; -import { getAnonymizedValue, transformRawData } from '@kbn/elastic-assistant-common'; +import { + getAnonymizedValue, + getOpenAndAcknowledgedAlertsQuery, + getRawDataOrDefault, + sizeIsOutOfRange, + transformRawData, +} from '@kbn/elastic-assistant-common'; import { DynamicStructuredTool } from '@langchain/core/tools'; import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; import { z } from '@kbn/zod'; import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; -import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query'; -import { getRawDataOrDefault, sizeIsOutOfRange } from './helpers'; import { APP_UI_ID } from '../../../../common'; export interface OpenAndAcknowledgedAlertsToolParams extends AssistantToolParams { diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 0d369f3c620c4..ce79bd061548f 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -205,7 +205,6 @@ "@kbn/search-types", "@kbn/field-utils", "@kbn/core-saved-objects-api-server-mocks", - "@kbn/langchain", "@kbn/core-analytics-browser", "@kbn/core-i18n-browser", "@kbn/core-theme-browser", From 7c3887309cec54cc21e1abf8a2522afa49147712 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian <jpdjeredjian@gmail.com> Date: Tue, 15 Oct 2024 22:51:25 -0300 Subject: [PATCH 80/84] [Security Solution] Extend upgrade perform endpoint logic (#191439) Fixes: https://github.com/elastic/kibana/issues/166376 (main ticket) Fixes: https://github.com/elastic/kibana/issues/186544 (handling of specific fields) Fixes: https://github.com/elastic/kibana/issues/180195 (replace PATCH with PUT logic on rule upgrade) ## Summary - Enhances the `/upgrade/_perform` endpoint to upgrade rules in a way that works with prebuilt rules customized by users and resolve conflicts between user customizations and updates from Elastic. - Handles special fields under the hood (see below) - Replaces the update prebuilt rule logic to work with PUT instead of PATCH. ### Rough implementation plan - For each `upgradeableRule`, we attempt to build the payload necessary to pass to `upgradePrebuiltRules()`, which is of type `PrebuiltRuleAsset`. So we retrieve the field names from `FIELDS_PAYLOAD_BY_RULE_TYPE` and loop through them. - If any of those `field`s are non-upgreadable, (i.e. its value needs to be handled under the hood) we do so in `determineFieldUpgradeStatus`. - Otherwise, we continue to build a `FieldUpgradeSpecifier` for each field, which will help us determine if that field needs to be set to the base, current, target version, OR if it needs to be calculated as a MERGED value, or it is passed in the request payload as a RESOLVED value. - Notice that we are iterating over "flat" (non-grouped) fields which are part of the `PrebuiltRuleAsset` schema. This means that mapping is necessary between these flat fields and the diffable (grouped) fields that are used in the API contract, part of `DiffableRule`. For example, if we try to determine the value for the `query` field, we will need to look up for its value in the `eql_query` field if the target rule is `eql` or in `esql_query` if the target rule is `esql`. All these mappings can be found in `diffable_rule_fields_mappings.ts`. - Once a `FieldUpgradeSpecifier` has been retrieved for each field of the payload we are building, retrieve its actual value: either fetching it from the base, current or target versions of the rule, from the three way diff calculation, or retrieving it from the request payload if it resolved. - Do this for all upgreadable rules, and the pass the payload array into `upgradePrebuiltRules()`. - **IMPORTANT:** The upgrade prebuilt rules logic has been changed from PATCH to PUT. That means that if the next version of a rule removes a field, and the user updates to that target version, those fields will be undefined in the resulting rule. **Additional example:** a installs a rule, and creates a `timeline_id` for it rule by modifying it. If neither the next version (target version) still does not have a `timeline_id` field for it, and the user updates to that target version fully (without resolving the conflict), that field will not exist anymore in the resulting rule. ## Acceptance criteria - [x] Extend the contract of the API endpoint according to the [POC](https://github.com/elastic/kibana/pull/144060): - [x] Add the ability to pick the `MERGED` version for rule upgrades. If the `MERGED` version is selected, the diffs are recalculated and the rule fields are updated to the result of the diff calculation. This is only possible if all field diffs return a `conflict` value of either `NO`. If any fields returns a value of `NON_SOLVABLE` or `SOLVABLE`, reject the request with an error specifying that there are conflicts, and that they must be resolved on a per-field basis. - [x] Calculate diffs inside this endpoint, when the value of `pick_version` is `MERGED`. - [x] Add the ability to specify rule field versions, to update specific fields to different `pick_versions`: `BASE' | 'CURRENT' | 'TARGET' | 'MERGED' | 'RESOLVED'` (See `FieldUpgradeRequest` in [PoC](https://github.com/elastic/kibana/pull/144060) for details) ## Handling of special fields Specific fields are handled under the hood based on https://github.com/elastic/kibana/issues/186544 See implementation in `x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/determine_field_upgrade_status.ts`, which imports fields to handle under the hood: - `DiffableFieldsToOmit` - `FieldsToUpdateToCurrentVersion` ## Edge cases - [x] If target version of rule has a **rule type change**, check that all `pick_version`, at all levels, match `TARGET`. Otherwise, create new error and add to ruleErrors array. - [x] if a rule has a specific `targetVersion.type` (for example, EQL) and the user includes in its `fields` object of the request payload any fields which do not match that rule type (in this case, for example, sending in `machine_learning_job_id` as part of `fields`), throw an error for that rule. - [x] Calculation of field diffs: what happens if some fields have a conflict value of `NON_SOLVABLE`: - [x] If the whole rule is being updated to `MERGED`, and **ANY** fields return with a `NON_SOLVABLE` conflict, reject the whole update for that rule: create new error and add to ruleErrors array. - [x] **EXCEPTION** for case above: the whole rule is being updated to `MERGED`, and one or more of the fields return with a `NON_SOLVABLE` conflict, BUT those same fields have a specific `pick_version` for them in the `fields` object which **ARE NOT** `MERGED`. No error should be reported in this case. - [x] The whole rule is being updated to any `pick_version` other than MERGED, but any specific field in the `fields` object is set to upgrade to `MERGED`, and the diff for that fields returns a `NON_SOLVABLE` conflict. In that case, create new error and add to ruleErrors array. ### TODO - [[Security Solution] Add InvestigationFields and AlertSuppression fields to the upgrade workflow [#190597]](https://github.com/elastic/kibana/issues/190597): InvestigationFields is already working, but AlertSuppression is still currently handled under the hood to update to current version. ### 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) --------- Co-authored-by: Maxim Palenov <maxim.palenov@elastic.co> --- .../model/diff/diffable_rule/diffable_rule.ts | 42 +- .../perform_rule_upgrade_route.ts | 51 +- .../get_prebuilt_rules_status_route.ts | 4 +- .../perform_rule_installation_route.ts | 4 +- ...rt_diffable_fields_match_rule_type.test.ts | 60 ++ .../assert_diffable_fields_match_rule_type.ts | 40 + .../assert_pick_version_is_target.test.ts | 131 +++ .../assert_pick_version_is_target.ts | 48 + .../create_field_upgrade_specifier.test.ts | 118 +++ .../create_field_upgrade_specifier.ts | 72 ++ .../create_props_to_rule_type_map.ts | 43 + .../create_upgradeable_rules_payload.ts | 145 +++ .../diffable_rule_fields_mappings.ts | 211 +++++ .../get_field_predefined_value.test.ts | 65 ++ .../get_field_predefined_value.ts | 73 ++ .../get_upgradeable_rules.test.ts | 191 ++++ .../get_upgradeable_rules.ts | 83 ++ .../get_value_for_field.ts | 94 ++ .../get_value_from_rule_version.ts | 94 ++ .../perform_rule_upgrade_route.ts | 122 +-- .../review_rule_installation_route.ts | 4 +- .../review_rule_upgrade_route.ts | 4 +- .../prebuilt_rule_assets_client.ts | 2 +- .../fetch_rule_versions_triad.ts | 2 +- .../rule_versions/rule_version_specifier.ts | 0 .../rule_assets/prebuilt_rule_asset.mock.ts | 200 +++- .../model/rule_assets/prebuilt_rule_asset.ts | 30 +- .../get_rule_groups.ts} | 34 +- .../mergers/apply_rule_patch.ts | 2 + .../methods/upgrade_prebuilt_rule.ts | 14 +- .../update_actions.ts | 14 + .../get_prebuilt_rules_status.ts | 16 +- .../trial_license_complete_tier/index.ts | 2 + ...e_perform_prebuilt_rules.all_rules_mode.ts | 490 ++++++++++ ...form_prebuilt_rules.specific_rules_mode.ts | 861 ++++++++++++++++++ .../upgrade_prebuilt_rules.ts | 25 +- ...prebuilt_rules_with_historical_versions.ts | 12 +- .../update_prebuilt_rules_package.ts | 15 +- .../export_rules.ts | 1 + .../get_custom_query_rule_params.ts | 1 + .../create_prebuilt_rule_saved_objects.ts | 29 +- .../utils/rules/prebuilt_rules/index.ts | 2 +- ...s.ts => perform_upgrade_prebuilt_rules.ts} | 19 +- 43 files changed, 3202 insertions(+), 268 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_props_to_rule_type_map.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts rename x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/{model => logic}/rule_versions/rule_version_specifier.ts (100%) rename x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/{rule_versions/get_version_buckets.ts => rule_groups/get_rule_groups.ts} (75%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/{upgrade_prebuilt_rules.ts => perform_upgrade_prebuilt_rules.ts} (67%) diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts index d0a4aa12533e0..6e24b902995f4 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts @@ -174,6 +174,17 @@ export const DiffableNewTermsFields = z.object({ alert_suppression: AlertSuppression.optional(), }); +export const DiffableFieldsByTypeUnion = z.discriminatedUnion('type', [ + DiffableCustomQueryFields, + DiffableSavedQueryFields, + DiffableEqlFields, + DiffableEsqlFields, + DiffableThreatMatchFields, + DiffableThresholdFields, + DiffableMachineLearningFields, + DiffableNewTermsFields, +]); + /** * Represents a normalized rule object that is suitable for passing to the diff algorithm. * Every top-level field of a diffable rule can be compared separately on its own. @@ -200,18 +211,6 @@ export const DiffableNewTermsFields = z.object({ * NOTE: Every top-level field in a DiffableRule MUST BE LOGICALLY INDEPENDENT from other * top-level fields. */ - -export const DiffableFieldsByTypeUnion = z.discriminatedUnion('type', [ - DiffableCustomQueryFields, - DiffableSavedQueryFields, - DiffableEqlFields, - DiffableEsqlFields, - DiffableThreatMatchFields, - DiffableThresholdFields, - DiffableMachineLearningFields, - DiffableNewTermsFields, -]); - export type DiffableRule = z.infer<typeof DiffableRule>; export const DiffableRule = z.intersection(DiffableCommonFields, DiffableFieldsByTypeUnion); @@ -246,3 +245,22 @@ export const DiffableAllFields = DiffableCommonFields.merge( .merge(DiffableMachineLearningFields.omit({ type: true })) .merge(DiffableNewTermsFields.omit({ type: true })) .merge(z.object({ type: DiffableRuleTypes })); + +const getRuleTypeFields = (schema: z.ZodObject<z.ZodRawShape>): string[] => + Object.keys(schema.shape); + +const createDiffableFieldsPerRuleType = (specificFields: z.ZodObject<z.ZodRawShape>): string[] => [ + ...getRuleTypeFields(DiffableCommonFields), + ...getRuleTypeFields(specificFields), +]; + +export const DIFFABLE_RULE_TYPE_FIELDS_MAP = new Map<DiffableRuleTypes, string[]>([ + ['query', createDiffableFieldsPerRuleType(DiffableCustomQueryFields)], + ['saved_query', createDiffableFieldsPerRuleType(DiffableSavedQueryFields)], + ['eql', createDiffableFieldsPerRuleType(DiffableEqlFields)], + ['esql', createDiffableFieldsPerRuleType(DiffableEsqlFields)], + ['threat_match', createDiffableFieldsPerRuleType(DiffableThreatMatchFields)], + ['threshold', createDiffableFieldsPerRuleType(DiffableThresholdFields)], + ['machine_learning', createDiffableFieldsPerRuleType(DiffableMachineLearningFields)], + ['new_terms', createDiffableFieldsPerRuleType(DiffableNewTermsFields)], +]); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts index c7d3227ef03f3..784f75d09bd7a 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -11,11 +11,52 @@ import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; import { AggregatedPrebuiltRuleError, DiffableAllFields } from '../model'; import { RuleSignatureId, RuleVersion } from '../../model'; +export type Mode = z.infer<typeof Mode>; +export const Mode = z.enum(['ALL_RULES', 'SPECIFIC_RULES']); +export type ModeEnum = typeof Mode.enum; +export const ModeEnum = Mode.enum; + export type PickVersionValues = z.infer<typeof PickVersionValues>; export const PickVersionValues = z.enum(['BASE', 'CURRENT', 'TARGET', 'MERGED']); export type PickVersionValuesEnum = typeof PickVersionValues.enum; export const PickVersionValuesEnum = PickVersionValues.enum; +// Specific handling of special fields according to: +// https://github.com/elastic/kibana/issues/186544 +export const FIELDS_TO_UPGRADE_TO_CURRENT_VERSION = [ + 'enabled', + 'exceptions_list', + 'alert_suppression', + 'actions', + 'throttle', + 'response_actions', + 'meta', + 'output_index', + 'namespace', + 'alias_purpose', + 'alias_target_id', + 'outcome', + 'concurrent_searches', + 'items_per_search', +] as const; + +export const NON_UPGRADEABLE_DIFFABLE_FIELDS = [ + 'type', + 'rule_id', + 'version', + 'author', + 'license', +] as const; + +type NON_UPGRADEABLE_DIFFABLE_FIELDS_TO_OMIT_TYPE = { + readonly [key in (typeof NON_UPGRADEABLE_DIFFABLE_FIELDS)[number]]: true; +}; + +// This transformation is needed to have Zod's `omit` accept the rule fields that need to be omitted +export const DiffableFieldsToOmit = NON_UPGRADEABLE_DIFFABLE_FIELDS.reduce((acc, field) => { + return { ...acc, [field]: true }; +}, {} as NON_UPGRADEABLE_DIFFABLE_FIELDS_TO_OMIT_TYPE); + /** * Fields upgradable by the /upgrade/_perform endpoint. * Specific fields are omitted because they are not upgradeable, and @@ -23,18 +64,12 @@ export const PickVersionValuesEnum = PickVersionValues.enum; * See: https://github.com/elastic/kibana/issues/186544 */ export type DiffableUpgradableFields = z.infer<typeof DiffableUpgradableFields>; -export const DiffableUpgradableFields = DiffableAllFields.omit({ - type: true, - rule_id: true, - version: true, - author: true, - license: true, -}); +export const DiffableUpgradableFields = DiffableAllFields.omit(DiffableFieldsToOmit); export type FieldUpgradeSpecifier<T> = z.infer< ReturnType<typeof fieldUpgradeSpecifier<z.ZodType<T>>> >; -const fieldUpgradeSpecifier = <T extends z.ZodTypeAny>(fieldSchema: T) => +export const fieldUpgradeSpecifier = <T extends z.ZodTypeAny>(fieldSchema: T) => z.discriminatedUnion('pick_version', [ z .object({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts index a5596ca4c8498..86809a3a79a93 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts @@ -13,7 +13,7 @@ import { buildSiemResponse } from '../../../routes/utils'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; -import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; +import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -44,7 +44,7 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter ruleObjectsClient, }); const { currentRules, installableRules, upgradeableRules, totalAvailableRules } = - getVersionBuckets(ruleVersionsMap); + getRuleGroups(ruleVersionsMap); const body: GetPrebuiltRulesStatusResponseBody = { stats: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts index 8ffec60a26c11..1a29568ca496b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts @@ -25,9 +25,9 @@ import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt import { createPrebuiltRules } from '../../logic/rule_objects/create_prebuilt_rules'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; -import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; import { PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS } from '../../constants'; +import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const performRuleInstallationRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -80,7 +80,7 @@ export const performRuleInstallationRoute = (router: SecuritySolutionPluginRoute ruleObjectsClient, versionSpecifiers: mode === 'ALL_RULES' ? undefined : request.body.rules, }); - const { currentRules, installableRules } = getVersionBuckets(ruleVersionsMap); + const { currentRules, installableRules } = getRuleGroups(ruleVersionsMap); // Perform all the checks we can before we start the upgrade process if (mode === 'SPECIFIC_RULES') { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.test.ts new file mode 100644 index 0000000000000..a7ff15a82a3db --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { assertDiffableFieldsMatchRuleType } from './assert_diffable_fields_match_rule_type'; +import { DIFFABLE_RULE_TYPE_FIELDS_MAP } from '../../../../../../common/api/detection_engine'; + +describe('assertDiffableFieldsMatchRuleType', () => { + describe('valid scenarios -', () => { + it('should validate all fields in DIFFABLE_RULE_TYPE_FIELDS_MAP', () => { + DIFFABLE_RULE_TYPE_FIELDS_MAP.forEach((fields, ruleType) => { + expect(() => { + assertDiffableFieldsMatchRuleType(fields, ruleType); + }).not.toThrow(); + }); + }); + + it('should not throw an error for valid upgradeable fields', () => { + expect(() => { + assertDiffableFieldsMatchRuleType(['name', 'description', 'severity'], 'query'); + }).not.toThrow(); + }); + + it('should handle valid rule type correctly', () => { + expect(() => { + assertDiffableFieldsMatchRuleType(['eql_query'], 'eql'); + }).not.toThrow(); + }); + + it('should handle empty upgradeable fields array', () => { + expect(() => { + assertDiffableFieldsMatchRuleType([], 'query'); + }).not.toThrow(); + }); + }); + + describe('invalid scenarios -', () => { + it('should throw an error for invalid upgradeable fields', () => { + expect(() => { + assertDiffableFieldsMatchRuleType(['invalid_field'], 'query'); + }).toThrow("invalid_field is not a valid upgradeable field for type 'query'"); + }); + + it('should throw for incompatible rule types', () => { + expect(() => { + assertDiffableFieldsMatchRuleType(['eql_query'], 'query'); + }).toThrow("eql_query is not a valid upgradeable field for type 'query'"); + }); + + it('should throw an error for an unknown rule type', () => { + expect(() => { + // @ts-expect-error - unknown rule + assertDiffableFieldsMatchRuleType(['name'], 'unknown_type'); + }).toThrow(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.ts new file mode 100644 index 0000000000000..14ac905ca885d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_diffable_fields_match_rule_type.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DiffableRuleTypes } from '../../../../../../common/api/detection_engine'; +import { DIFFABLE_RULE_TYPE_FIELDS_MAP } from '../../../../../../common/api/detection_engine'; + +/** + * Validates that the upgradeable (diffable) fields match the target rule type's diffable fields. + * + * This function is used in the rule upgrade process to ensure that the fields + * specified for upgrade in the request body are valid for the target rule type. + * It checks each upgradeable field provided in body.rule[].fields against the + * set of diffable fields for the target rule type. + * + * @param {string[]} diffableFields - An array of field names to be upgraded. + * @param {string} ruleType - A rule type (e.g., 'query', 'eql', 'machine_learning'). + * @throws {Error} If an upgradeable field is not valid for the target rule type. + * + * @examples + * assertDiffableFieldsMatchRuleType(['kql_query', 'severity'], 'query'); + * assertDiffableFieldsMatchRuleType(['esql_query', 'description'], 'esql'); + * assertDiffableFieldsMatchRuleType(['machine_learning_job_id'], 'eql'); // throws error + * + * @see {@link DIFFABLE_RULE_TYPE_FIELDS_MAP} in diffable_rule.ts for the mapping of rule types to their diffable fields. + */ +export const assertDiffableFieldsMatchRuleType = ( + diffableFields: string[], + ruleType: DiffableRuleTypes +) => { + const diffableFieldsForType = new Set(DIFFABLE_RULE_TYPE_FIELDS_MAP.get(ruleType)); + for (const diffableField of diffableFields) { + if (!diffableFieldsForType.has(diffableField)) { + throw new Error(`${diffableField} is not a valid upgradeable field for type '${ruleType}'`); + } + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.test.ts new file mode 100644 index 0000000000000..d4cd1ae010067 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { assertPickVersionIsTarget } from './assert_pick_version_is_target'; +import type { + PerformRuleUpgradeRequestBody, + PickVersionValues, +} from '../../../../../../common/api/detection_engine'; + +describe('assertPickVersionIsTarget', () => { + const ruleId = 'test-rule-id'; + const createExpectedError = (id: string) => + `Rule update for rule ${id} has a rule type change. All 'pick_version' values for rule must match 'TARGET'`; + + describe('valid cases - ', () => { + it('should not throw when pick_version is TARGET for ALL_RULES mode', () => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).not.toThrow(); + }); + + it('should not throw when pick_version is TARGET for SPECIFIC_RULES mode', () => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'SPECIFIC_RULES', + rules: [ + { + rule_id: ruleId, + revision: 1, + version: 1, + pick_version: 'TARGET', + }, + ], + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).not.toThrow(); + }); + + it('should not throw when all pick_version values are TARGET', () => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'SPECIFIC_RULES', + pick_version: 'TARGET', + rules: [ + { + rule_id: ruleId, + revision: 1, + version: 1, + pick_version: 'TARGET', + fields: { + name: { pick_version: 'TARGET' }, + description: { pick_version: 'TARGET' }, + }, + }, + ], + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).not.toThrow(); + }); + }); + + describe('invalid cases - ', () => { + it('should throw when pick_version is not TARGET for ALL_RULES mode', () => { + const pickVersions: PickVersionValues[] = ['BASE', 'CURRENT', 'MERGED']; + + pickVersions.forEach((pickVersion) => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'ALL_RULES', + pick_version: pickVersion, + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).toThrowError( + createExpectedError(ruleId) + ); + }); + }); + + it('should throw when pick_version is not TARGET for SPECIFIC_RULES mode', () => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'SPECIFIC_RULES', + rules: [ + { + rule_id: ruleId, + revision: 1, + version: 1, + pick_version: 'BASE', + }, + ], + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).toThrowError( + createExpectedError(ruleId) + ); + }); + + it('should throw when any field-specific pick_version is not TARGET', () => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'SPECIFIC_RULES', + rules: [ + { + rule_id: ruleId, + revision: 1, + version: 1, + pick_version: 'TARGET', + fields: { + name: { pick_version: 'BASE' }, + }, + }, + ], + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).toThrowError( + createExpectedError(ruleId) + ); + }); + + it('should throw when pick_version is missing (defaults to MERGED)', () => { + const requestBody: PerformRuleUpgradeRequestBody = { + mode: 'SPECIFIC_RULES', + rules: [{ rule_id: ruleId, revision: 1, version: 1 }], + }; + + expect(() => assertPickVersionIsTarget({ requestBody, ruleId })).toThrow(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.ts new file mode 100644 index 0000000000000..63e67512be249 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/assert_pick_version_is_target.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { + PerformRuleUpgradeRequestBody, + PickVersionValues, +} from '../../../../../../common/api/detection_engine'; + +interface AssertRuleTypeMatchProps { + requestBody: PerformRuleUpgradeRequestBody; + ruleId: string; +} + +/* + * Assert that, in the case where the rule is undergoing a rule type change, + * the pick_version value is set to 'TARGET' at all levels (global, rule-specific and field-specific) + */ +export const assertPickVersionIsTarget = ({ requestBody, ruleId }: AssertRuleTypeMatchProps) => { + const pickVersions: Array<PickVersionValues | 'RESOLVED'> = []; + + if (requestBody.mode === 'SPECIFIC_RULES') { + const rulePayload = requestBody.rules.find((rule) => rule.rule_id === ruleId); + + // Rule-level pick_version overrides global pick_version. Pick rule-level pick_version if it + // exists, otherwise use global pick_version. If none exist, we default to 'MERGED'. + pickVersions.push(rulePayload?.pick_version ?? requestBody.pick_version ?? 'MERGED'); + + if (rulePayload?.fields) { + const fieldPickValues = Object.values(rulePayload?.fields).map((field) => field.pick_version); + pickVersions.push(...fieldPickValues); + } + } else { + // mode: ALL_RULES + pickVersions.push(requestBody.pick_version ?? 'MERGED'); + } + + const allPickVersionsAreTarget = pickVersions.every((version) => version === 'TARGET'); + + // If pick_version is provided at any levels, they must all be set to 'TARGET' + if (!allPickVersionsAreTarget) { + throw new Error( + `Rule update for rule ${ruleId} has a rule type change. All 'pick_version' values for rule must match 'TARGET'` + ); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.test.ts new file mode 100644 index 0000000000000..ac5db9ef1e7f2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createFieldUpgradeSpecifier } from './create_field_upgrade_specifier'; +import { + PickVersionValuesEnum, + type DiffableRuleTypes, +} from '../../../../../../common/api/detection_engine'; +import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; + +describe('createFieldUpgradeSpecifier', () => { + const defaultArgs = { + fieldName: 'name' as keyof PrebuiltRuleAsset, + globalPickVersion: PickVersionValuesEnum.MERGED, + ruleId: 'rule-1', + targetRuleType: 'query' as DiffableRuleTypes, + }; + + it('should return rule-specific pick version when no specific fields are defined', () => { + const result = createFieldUpgradeSpecifier({ + ...defaultArgs, + ruleUpgradeSpecifier: { + rule_id: 'rule-1', + pick_version: PickVersionValuesEnum.BASE, + revision: 1, + version: 1, + }, + }); + expect(result).toEqual({ pick_version: PickVersionValuesEnum.BASE }); + }); + + it('should return field-specific pick version when defined', () => { + const result = createFieldUpgradeSpecifier({ + ...defaultArgs, + fieldName: 'description', + ruleUpgradeSpecifier: { + rule_id: 'rule-1', + pick_version: PickVersionValuesEnum.TARGET, + revision: 1, + version: 1, + fields: { description: { pick_version: PickVersionValuesEnum.CURRENT } }, + }, + }); + expect(result).toEqual({ + pick_version: PickVersionValuesEnum.CURRENT, + }); + }); + + it('should return resolved value for specifc fields with RESOLVED pick versions', () => { + const result = createFieldUpgradeSpecifier({ + ...defaultArgs, + fieldName: 'description', + ruleUpgradeSpecifier: { + rule_id: 'rule-1', + revision: 1, + version: 1, + fields: { + description: { pick_version: 'RESOLVED', resolved_value: 'New description' }, + }, + }, + }); + expect(result).toEqual({ + pick_version: 'RESOLVED', + resolved_value: 'New description', + }); + }); + + it('should handle fields that require mapping', () => { + const result = createFieldUpgradeSpecifier({ + ...defaultArgs, + fieldName: 'index' as keyof PrebuiltRuleAsset, + ruleUpgradeSpecifier: { + rule_id: 'rule-1', + revision: 1, + version: 1, + fields: { data_source: { pick_version: PickVersionValuesEnum.CURRENT } }, + }, + }); + expect(result).toEqual({ pick_version: PickVersionValuesEnum.CURRENT }); + }); + + it('should fall back to rule-level pick version when field is not specified', () => { + const result = createFieldUpgradeSpecifier({ + ...defaultArgs, + fieldName: 'description', + ruleUpgradeSpecifier: { + rule_id: 'rule-1', + pick_version: PickVersionValuesEnum.TARGET, + revision: 1, + version: 1, + fields: { name: { pick_version: PickVersionValuesEnum.CURRENT } }, + }, + }); + expect(result).toEqual({ + pick_version: PickVersionValuesEnum.TARGET, + }); + }); + + it('should throw error if field is not a valid upgradeable field', () => { + // machine_learning_job_id field does not match 'eql' target rule type + expect(() => + createFieldUpgradeSpecifier({ + ...defaultArgs, + targetRuleType: 'eql', + ruleUpgradeSpecifier: { + rule_id: 'rule-1', + revision: 1, + version: 1, + fields: { machine_learning_job_id: { pick_version: PickVersionValuesEnum.CURRENT } }, + }, + }) + ).toThrowError(`machine_learning_job_id is not a valid upgradeable field for type 'eql'`); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.ts new file mode 100644 index 0000000000000..7526394c5a75f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_field_upgrade_specifier.ts @@ -0,0 +1,72 @@ +/* + * 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 { assertDiffableFieldsMatchRuleType } from './assert_diffable_fields_match_rule_type'; +import { + type UpgradeSpecificRulesRequest, + type RuleFieldsToUpgrade, + type DiffableRuleTypes, + type FieldUpgradeSpecifier, + type PickVersionValues, +} from '../../../../../../common/api/detection_engine'; +import { type PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import { mapRuleFieldToDiffableRuleField } from './diffable_rule_fields_mappings'; + +interface CreateFieldUpgradeSpecifierArgs { + fieldName: keyof PrebuiltRuleAsset; + ruleUpgradeSpecifier: UpgradeSpecificRulesRequest['rules'][number]; + targetRuleType: DiffableRuleTypes; + globalPickVersion: PickVersionValues; +} + +/** + * Creates a field upgrade specifier for a given field in PrebuiltRuleAsset. + * + * This function determines how a specific field should be upgraded based on the + * upgrade request body and the pick_version at global, rule and field-levels, + * when the mode is SPECIFIC_RULES. + */ +export const createFieldUpgradeSpecifier = ({ + fieldName, + ruleUpgradeSpecifier, + targetRuleType, + globalPickVersion, +}: CreateFieldUpgradeSpecifierArgs): FieldUpgradeSpecifier<unknown> => { + if (!ruleUpgradeSpecifier.fields || Object.keys(ruleUpgradeSpecifier.fields).length === 0) { + return { + pick_version: ruleUpgradeSpecifier.pick_version ?? globalPickVersion, + }; + } + + assertDiffableFieldsMatchRuleType(Object.keys(ruleUpgradeSpecifier.fields), targetRuleType); + + const fieldsToUpgradePayload = ruleUpgradeSpecifier.fields as Record< + string, + RuleFieldsToUpgrade[keyof RuleFieldsToUpgrade] + >; + + const fieldGroup = mapRuleFieldToDiffableRuleField({ + ruleType: targetRuleType, + fieldName, + }); + + const fieldUpgradeSpecifier = fieldsToUpgradePayload[fieldGroup]; + + if (fieldUpgradeSpecifier?.pick_version === 'RESOLVED') { + return { + pick_version: 'RESOLVED', + resolved_value: fieldUpgradeSpecifier.resolved_value, + }; + } + + return { + pick_version: + // If there's no matching specific field upgrade specifier in the payload, + // we fallback to a rule level pick_version. Since this is also optional, + // we default to the global pick_version. + fieldUpgradeSpecifier?.pick_version ?? ruleUpgradeSpecifier.pick_version ?? globalPickVersion, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_props_to_rule_type_map.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_props_to_rule_type_map.ts new file mode 100644 index 0000000000000..d0b798fabaeb6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_props_to_rule_type_map.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + SharedCreateProps, + TypeSpecificCreatePropsInternal, +} from '../../../../../../common/api/detection_engine'; +import { type PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; + +function createRuleTypeToCreateRulePropsMap() { + // SharedCreateProps is an extension of BaseCreateProps, but includes rule_id + const baseFields = Object.keys(SharedCreateProps.shape); + + return new Map( + TypeSpecificCreatePropsInternal.options.map((option) => { + const typeName = option.shape.type.value; + const typeSpecificFieldsForType = Object.keys(option.shape); + + return [typeName, [...baseFields, ...typeSpecificFieldsForType] as [keyof PrebuiltRuleAsset]]; + }) + ); +} + +/** + * Map of the CreateProps field names, by rule type. + * + * Helps creating the payload to be passed to the `upgradePrebuiltRules()` method during the + * Upgrade workflow (`/upgrade/_perform` endpoint) + * + * Creating this Map dynamically, based on BaseCreateProps and TypeSpecificFields, ensures that we don't need to: + * - manually add rule types to this Map if they are created + * - manually add or remove any fields if they are added or removed to a specific rule type + * - manually add or remove any fields if we decide that they should not be part of the upgradable fields. + * + * Notice that this Map includes, for each rule type, all fields that are part of the BaseCreateProps and all fields that + * are part of the TypeSpecificFields, including those that are not part of RuleUpgradeSpecifierFields schema, where + * the user of the /upgrade/_perform endpoint can specify which fields to upgrade during the upgrade workflow. + */ +export const FIELD_NAMES_BY_RULE_TYPE_MAP = createRuleTypeToCreateRulePropsMap(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts new file mode 100644 index 0000000000000..97e587646e524 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts @@ -0,0 +1,145 @@ +/* + * 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 { pickBy } from 'lodash'; +import type { PromisePoolError } from '../../../../../utils/promise_pool'; +import { + PickVersionValuesEnum, + type PerformRuleUpgradeRequestBody, + type PickVersionValues, + type AllFieldsDiff, + MissingVersion, +} from '../../../../../../common/api/detection_engine'; +import { convertRuleToDiffable } from '../../../../../../common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable'; +import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import { assertPickVersionIsTarget } from './assert_pick_version_is_target'; +import { FIELD_NAMES_BY_RULE_TYPE_MAP } from './create_props_to_rule_type_map'; +import { calculateRuleFieldsDiff } from '../../logic/diff/calculation/calculate_rule_fields_diff'; +import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; +import { getValueForField } from './get_value_for_field'; + +interface CreateModifiedPrebuiltRuleAssetsProps { + upgradeableRules: RuleTriad[]; + requestBody: PerformRuleUpgradeRequestBody; +} + +interface ProcessedRules { + modifiedPrebuiltRuleAssets: PrebuiltRuleAsset[]; + processingErrors: Array<PromisePoolError<{ rule_id: string }>>; +} + +export const createModifiedPrebuiltRuleAssets = ({ + upgradeableRules, + requestBody, +}: CreateModifiedPrebuiltRuleAssetsProps) => { + const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; + + const { modifiedPrebuiltRuleAssets, processingErrors } = upgradeableRules.reduce<ProcessedRules>( + (processedRules, upgradeableRule) => { + const targetRuleType = upgradeableRule.target.type; + const ruleId = upgradeableRule.target.rule_id; + const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); + + try { + if (fieldNames === undefined) { + throw new Error(`Unexpected rule type: ${targetRuleType}`); + } + + const { current, target } = upgradeableRule; + if (current.type !== target.type) { + assertPickVersionIsTarget({ ruleId, requestBody }); + } + + const calculatedRuleDiff = calculateRuleFieldsDiff({ + base_version: upgradeableRule.base + ? convertRuleToDiffable(convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base)) + : MissingVersion, + current_version: convertRuleToDiffable(upgradeableRule.current), + target_version: convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) + ), + }) as AllFieldsDiff; + + if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { + const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); + if (fieldsWithConflicts.length > 0) { + // If the mode is ALL_RULES, no fields can be overriden to any other pick_version + // than "MERGED", so throw an error for the fields that have conflicts. + throw new Error( + `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( + ', ' + )}. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + } + } + + const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ + upgradeableRule, + fieldNames, + requestBody, + globalPickVersion, + calculatedRuleDiff, + }); + + processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); + + return processedRules; + } catch (err) { + processedRules.processingErrors.push({ + error: err, + item: { rule_id: ruleId }, + }); + + return processedRules; + } + }, + { + modifiedPrebuiltRuleAssets: [], + processingErrors: [], + } + ); + + return { + modifiedPrebuiltRuleAssets, + processingErrors, + }; +}; + +interface CreateModifiedPrebuiltRuleAssetParams { + upgradeableRule: RuleTriad; + fieldNames: Array<keyof PrebuiltRuleAsset>; + globalPickVersion: PickVersionValues; + requestBody: PerformRuleUpgradeRequestBody; + calculatedRuleDiff: AllFieldsDiff; +} + +function createModifiedPrebuiltRuleAsset({ + upgradeableRule, + fieldNames, + globalPickVersion, + requestBody, + calculatedRuleDiff, +}: CreateModifiedPrebuiltRuleAssetParams): PrebuiltRuleAsset { + const modifiedPrebuiltRuleAsset = {} as Record<string, unknown>; + + for (const fieldName of fieldNames) { + modifiedPrebuiltRuleAsset[fieldName] = getValueForField({ + fieldName, + upgradeableRule, + globalPickVersion, + requestBody, + ruleFieldsDiff: calculatedRuleDiff, + }); + } + + return modifiedPrebuiltRuleAsset as PrebuiltRuleAsset; +} + +const getFieldsDiffConflicts = (ruleFieldsDiff: Partial<AllFieldsDiff>) => + pickBy(ruleFieldsDiff, (diff) => { + return diff.conflict !== 'NONE'; + }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts new file mode 100644 index 0000000000000..d56747f9db264 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts @@ -0,0 +1,211 @@ +/* + * 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 { get } from 'lodash'; +import type { + RuleSchedule, + InlineKqlQuery, + ThreeWayDiff, + DiffableRuleTypes, +} from '../../../../../../common/api/detection_engine'; +import { type AllFieldsDiff } from '../../../../../../common/api/detection_engine'; +import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; + +/** + * Retrieves and transforms the value for a specific field from a DiffableRule group. + * + * Maps PrebuiltRuleAsset schema fields to their corresponding DiffableRule group values. It also + * applies necessary transformations to ensure the returned value matches the expected format + * for the PrebuiltRuleAsset schema. + * + * @param {keyof PrebuiltRuleAsset} field - The field name in the PrebuiltRuleAsset schema. + * @param {ThreeWayDiff<unknown>['merged_version']} diffableField - The corresponding field value from the DiffableRule. + * + * @example + * // For an 'index' field + * mapDiffableRuleFieldValueToRuleSchema('index', { index_patterns: ['logs-*'] }) + * // Returns: ['logs-*'] + * + * @example + * // For a 'from' field in a rule schedule + * mapDiffableRuleFieldValueToRuleSchema('from', { interval: '5d', lookback: '30d' }) + * // Returns: 'now-30d' + * + */ +export const mapDiffableRuleFieldValueToRuleSchemaFormat = ( + fieldName: keyof PrebuiltRuleAsset, + diffableField: ThreeWayDiff<unknown>['merged_version'] +) => { + const diffableRuleSubfieldName = mapRuleFieldToDiffableRuleSubfield(fieldName); + + const transformedValue = transformDiffableFieldValues(fieldName, diffableField); + if (transformedValue.type === 'TRANSFORMED_FIELD') { + return transformedValue.value; + } + + // From the ThreeWayDiff, get the specific field that maps to the diffable rule field + // Otherwise, the diffableField itself already matches the rule field, so retrieve that value. + const mappedField = get(diffableField, diffableRuleSubfieldName, diffableField); + + return mappedField; +}; + +interface MapRuleFieldToDiffableRuleFieldParams { + ruleType: DiffableRuleTypes; + fieldName: string; +} +/** + * Maps a PrebuiltRuleAsset schema field name to its corresponding DiffableRule group. + * + * Determines which group in the DiffableRule schema a given field belongs to. Handles special + * cases for query-related fields based on the rule type. + * + * @param {string} fieldName - The field name from the PrebuiltRuleAsset schema. + * @param {string} ruleType - The type of the rule being processed. + * + * @example + * mapRuleFieldToDiffableRuleField('index', 'query') + * // Returns: 'data_source' + * + * @example + * mapRuleFieldToDiffableRuleField('query', 'eql') + * // Returns: 'eql_query' + * + */ +export function mapRuleFieldToDiffableRuleField({ + ruleType, + fieldName, +}: MapRuleFieldToDiffableRuleFieldParams): keyof AllFieldsDiff { + const diffableRuleFieldMap: Record<string, keyof AllFieldsDiff> = { + building_block_type: 'building_block', + saved_id: 'kql_query', + threat_query: 'threat_query', + threat_language: 'threat_query', + threat_filters: 'threat_query', + index: 'data_source', + data_view_id: 'data_source', + rule_name_override: 'rule_name_override', + interval: 'rule_schedule', + from: 'rule_schedule', + to: 'rule_schedule', + timeline_id: 'timeline_template', + timeline_title: 'timeline_template', + timestamp_override: 'timestamp_override', + timestamp_override_fallback_disabled: 'timestamp_override', + }; + + // Handle query, filters and language fields based on rule type + if (fieldName === 'query' || fieldName === 'language' || fieldName === 'filters') { + switch (ruleType) { + case 'query': + case 'saved_query': + return 'kql_query' as const; + case 'eql': + return 'eql_query'; + case 'esql': + return 'esql_query'; + default: + return 'kql_query'; + } + } + + return diffableRuleFieldMap[fieldName] || fieldName; +} + +/** + * Maps a PrebuiltRuleAsset schema field name to its corresponding property + * name within a DiffableRule group. + * + * @param {string} fieldName - The field name from the PrebuiltRuleAsset schema. + * @returns {string} The corresponding property name in the DiffableRule group. + * + * @example + * mapRuleFieldToDiffableRuleSubfield('index') + * // Returns: 'index_patterns' + * + * @example + * mapRuleFieldToDiffableRuleSubfield('from') + * // Returns: 'lookback' + * + */ +export function mapRuleFieldToDiffableRuleSubfield(fieldName: string): string { + const fieldMapping: Record<string, string> = { + index: 'index_patterns', + data_view_id: 'data_view_id', + saved_id: 'saved_query_id', + building_block_type: 'type', + rule_name_override: 'field_name', + timestamp_override: 'field_name', + timestamp_override_fallback_disabled: 'fallback_disabled', + timeline_id: 'timeline_id', + timeline_title: 'timeline_title', + interval: 'interval', + from: 'lookback', + to: 'lookback', + }; + + return fieldMapping[fieldName] || fieldName; +} + +type TransformValuesReturnType = + | { + type: 'TRANSFORMED_FIELD'; + value: unknown; + } + | { type: 'NON_TRANSFORMED_FIELD' }; + +/** + * Transforms specific field values from the DiffableRule format to the PrebuiltRuleAsset/RuleResponse format. + * + * This function is used in the rule upgrade process to ensure that certain fields + * are correctly formatted when creating the updated rules payload. It handles + * special cases where the format differs between the DiffableRule and the + * PrebuiltRuleAsset/RuleResponse schemas. + * + * @param {string} fieldName - The name of the field being processed. + * @param {RuleSchedule | InlineKqlQuery | unknown} diffableFieldValue - The value of the field in DiffableRule format. + * + * @returns {TransformValuesReturnType} An object indicating whether the field was transformed + * and its new value if applicable. + * - If transformed: { type: 'TRANSFORMED_FIELD', value: transformedValue } + * - If not transformed: { type: 'NON_TRANSFORMED_FIELD' } + * + * @example + * // Transforms 'from' field + * transformDiffableFieldValues('from', { lookback: '30d' }) + * // Returns: { type: 'TRANSFORMED_FIELD', value: 'now-30d' } + * + * @example + * // Transforms 'saved_id' field for inline queries + * transformDiffableFieldValues('saved_id', { type: 'inline_query', ... }) + * // Returns: { type: 'TRANSFORMED_FIELD', value: undefined } + * + */ +export const transformDiffableFieldValues = ( + fieldName: string, + diffableFieldValue: RuleSchedule | InlineKqlQuery | unknown +): TransformValuesReturnType => { + if (fieldName === 'from' && isRuleSchedule(diffableFieldValue)) { + return { type: 'TRANSFORMED_FIELD', value: `now-${diffableFieldValue.lookback}` }; + } else if (fieldName === 'to') { + return { type: 'TRANSFORMED_FIELD', value: `now` }; + } else if (fieldName === 'saved_id' && isInlineQuery(diffableFieldValue)) { + // saved_id should be set only for rules with SavedKqlQuery, undefined otherwise + return { type: 'TRANSFORMED_FIELD', value: undefined }; + } + + return { type: 'NON_TRANSFORMED_FIELD' }; +}; + +function isRuleSchedule(value: unknown): value is RuleSchedule { + return typeof value === 'object' && value !== null && 'lookback' in value; +} + +function isInlineQuery(value: unknown): value is InlineKqlQuery { + return ( + typeof value === 'object' && value !== null && 'type' in value && value.type === 'inline_query' + ); +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.test.ts new file mode 100644 index 0000000000000..9a1ca051c54fa --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { getFieldPredefinedValue } from './get_field_predefined_value'; +import { + NON_UPGRADEABLE_DIFFABLE_FIELDS, + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, +} from '../../../../../../common/api/detection_engine'; +import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; + +describe('getFieldPredefinedValue', () => { + const mockUpgradeableRule = { + current: { + rule_id: 'current_rule_id', + type: 'query', + enabled: true, + name: 'Current Rule', + description: 'Current description', + version: 1, + author: ['Current Author'], + license: 'Current License', + }, + target: { + rule_id: 'target_rule_id', + type: 'query', + enabled: false, + name: 'Target Rule', + description: 'Target description', + version: 2, + author: ['Target Author'], + license: 'Target License', + }, + } as RuleTriad; + + it('should return PREDEFINED_VALUE with target value for fields in NON_UPGRADEABLE_DIFFABLE_FIELDS', () => { + NON_UPGRADEABLE_DIFFABLE_FIELDS.forEach((field) => { + const result = getFieldPredefinedValue(field as keyof PrebuiltRuleAsset, mockUpgradeableRule); + expect(result).toEqual({ + type: 'PREDEFINED_VALUE', + value: mockUpgradeableRule.target[field as keyof PrebuiltRuleAsset], + }); + }); + }); + + it('should return PREDEFINED_VALUE with current value for fields in FIELDS_TO_UPGRADE_TO_CURRENT_VERSION', () => { + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + const result = getFieldPredefinedValue(field as keyof PrebuiltRuleAsset, mockUpgradeableRule); + expect(result).toEqual({ + type: 'PREDEFINED_VALUE', + value: mockUpgradeableRule.current[field as keyof PrebuiltRuleAsset], + }); + }); + }); + + it('should return CUSTOMIZABLE_VALUE for fields not in NON_UPGRADEABLE_DIFFABLE_FIELDS or FIELDS_TO_UPGRADE_TO_CURRENT_VERSION', () => { + const upgradeableField = 'description'; + const result = getFieldPredefinedValue(upgradeableField, mockUpgradeableRule); + expect(result).toEqual({ type: 'CUSTOMIZABLE_VALUE' }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.ts new file mode 100644 index 0000000000000..777711e56470c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_field_predefined_value.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, + NON_UPGRADEABLE_DIFFABLE_FIELDS, +} from '../../../../../../common/api/detection_engine'; +import { type PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; + +type GetFieldPredefinedValueReturnType = + | { + type: 'PREDEFINED_VALUE'; + value: unknown; + } + | { type: 'CUSTOMIZABLE_VALUE' }; + +/** + * Determines whether a field can be upgraded via API (i.e. whether it should take + * a predefined value or is customizable), and returns the value if it is predefined. + * + * This function checks whether a field can be upgraded via API contract and how it should + * be handled during the rule upgrade process. It uses the `NON_UPGRADEABLE_DIFFABLE_FIELDS` and + * `FIELDS_TO_UPGRADE_TO_CURRENT_VERSION` constants to make this determination. + * + * `NON_UPGRADEABLE_DIFFABLE_FIELDS` includes fields that are not upgradeable: 'type', 'rule_id', + * 'version', 'author', and 'license', and are always upgraded to the target version. + * + * `FIELDS_TO_UPGRADE_TO_CURRENT_VERSION` includes fields that should be updated to their + * current version, such as 'enabled', 'alert_suppression', 'actions', 'throttle', + * 'response_actions', 'meta', 'output_index', 'namespace', 'alias_purpose', + * 'alias_target_id', 'outcome', 'concurrent_searches', and 'items_per_search'. + * + * @param {keyof PrebuiltRuleAsset} fieldName - The field name to check for upgrade status. + * @param {RuleTriad} upgradeableRule - The rule object containing current and target versions. + * + * @returns {GetFieldPredefinedValueReturnType} An object indicating whether the field + * is upgradeable and its value to upgrade to if it's not upgradeable via API. + */ +export const getFieldPredefinedValue = ( + fieldName: keyof PrebuiltRuleAsset, + upgradeableRule: RuleTriad +): GetFieldPredefinedValueReturnType => { + if ( + NON_UPGRADEABLE_DIFFABLE_FIELDS.includes( + fieldName as (typeof NON_UPGRADEABLE_DIFFABLE_FIELDS)[number] + ) + ) { + return { + type: 'PREDEFINED_VALUE', + value: upgradeableRule.target[fieldName], + }; + } + + if ( + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.includes( + fieldName as (typeof FIELDS_TO_UPGRADE_TO_CURRENT_VERSION)[number] + ) + ) { + return { + type: 'PREDEFINED_VALUE', + value: upgradeableRule.current[fieldName], + }; + } + + return { + type: 'CUSTOMIZABLE_VALUE', + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts new file mode 100644 index 0000000000000..5b1c74825102c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts @@ -0,0 +1,191 @@ +/* + * 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 { getUpgradeableRules } from './get_upgradeable_rules'; +import { ModeEnum, SkipRuleUpgradeReasonEnum } from '../../../../../../common/api/detection_engine'; +import type { + RuleResponse, + RuleUpgradeSpecifier, +} from '../../../../../../common/api/detection_engine'; +import { getPrebuiltRuleMockOfType } from '../../model/rule_assets/prebuilt_rule_asset.mock'; +import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; + +describe('getUpgradeableRules', () => { + const baseRule = getPrebuiltRuleMockOfType('query'); + const createUpgradeableRule = ( + ruleId: string, + currentVersion: number, + targetVersion: number + ): RuleTriad => { + return { + current: { + ...baseRule, + rule_id: ruleId, + version: currentVersion, + revision: 0, + }, + target: { ...baseRule, rule_id: ruleId, version: targetVersion }, + } as RuleTriad; + }; + + const mockUpgradeableRule = createUpgradeableRule('rule-1', 1, 2); + + const mockCurrentRule: RuleResponse = { + ...convertPrebuiltRuleAssetToRuleResponse(baseRule), + rule_id: 'rule-1', + revision: 0, + version: 1, + }; + + describe('ALL_RULES mode', () => { + it('should return all upgradeable rules when in ALL_RULES mode', () => { + const result = getUpgradeableRules({ + rawUpgradeableRules: [mockUpgradeableRule], + currentRules: [mockCurrentRule], + mode: ModeEnum.ALL_RULES, + }); + + expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); + expect(result.fetchErrors).toEqual([]); + expect(result.skippedRules).toEqual([]); + }); + + it('should handle empty upgradeable rules list', () => { + const result = getUpgradeableRules({ + rawUpgradeableRules: [], + currentRules: [], + mode: ModeEnum.ALL_RULES, + }); + + expect(result.upgradeableRules).toEqual([]); + expect(result.fetchErrors).toEqual([]); + expect(result.skippedRules).toEqual([]); + }); + }); + + describe('SPECIFIC_RULES mode', () => { + const mockVersionSpecifier: RuleUpgradeSpecifier = { + rule_id: 'rule-1', + revision: 0, + version: 1, + }; + + it('should return specified upgradeable rules when in SPECIFIC_RULES mode', () => { + const result = getUpgradeableRules({ + rawUpgradeableRules: [mockUpgradeableRule], + currentRules: [mockCurrentRule], + versionSpecifiers: [mockVersionSpecifier], + mode: ModeEnum.SPECIFIC_RULES, + }); + + expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); + expect(result.fetchErrors).toEqual([]); + expect(result.skippedRules).toEqual([]); + }); + + it('should handle rule not found', () => { + const result = getUpgradeableRules({ + rawUpgradeableRules: [mockUpgradeableRule], + currentRules: [mockCurrentRule], + versionSpecifiers: [{ ...mockVersionSpecifier, rule_id: 'nonexistent' }], + mode: ModeEnum.SPECIFIC_RULES, + }); + + expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); + expect(result.fetchErrors).toHaveLength(1); + expect(result.fetchErrors[0].error.message).toContain( + 'Rule with rule_id "nonexistent" and version "1" not found' + ); + expect(result.skippedRules).toEqual([]); + }); + + it('should handle non-upgradeable rule', () => { + const nonUpgradeableRule: RuleResponse = { + ...convertPrebuiltRuleAssetToRuleResponse(baseRule), + rule_id: 'rule-2', + revision: 0, + version: 1, + }; + + const result = getUpgradeableRules({ + rawUpgradeableRules: [mockUpgradeableRule], + currentRules: [mockCurrentRule, nonUpgradeableRule], + versionSpecifiers: [mockVersionSpecifier, { ...mockVersionSpecifier, rule_id: 'rule-2' }], + mode: ModeEnum.SPECIFIC_RULES, + }); + + expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); + expect(result.fetchErrors).toEqual([]); + expect(result.skippedRules).toEqual([ + { rule_id: 'rule-2', reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE }, + ]); + }); + + it('should handle revision mismatch', () => { + const result = getUpgradeableRules({ + rawUpgradeableRules: [mockUpgradeableRule], + currentRules: [mockCurrentRule], + versionSpecifiers: [{ ...mockVersionSpecifier, revision: 1 }], + mode: ModeEnum.SPECIFIC_RULES, + }); + + expect(result.upgradeableRules).toEqual([]); + expect(result.fetchErrors).toHaveLength(1); + expect(result.fetchErrors[0].error.message).toContain( + 'Revision mismatch for rule_id rule-1: expected 0, got 1' + ); + expect(result.skippedRules).toEqual([]); + }); + + it('should handle multiple rules with mixed scenarios', () => { + const mockUpgradeableRule2 = createUpgradeableRule('rule-2', 1, 2); + const mockCurrentRule2: RuleResponse = { + ...convertPrebuiltRuleAssetToRuleResponse(baseRule), + rule_id: 'rule-2', + revision: 0, + version: 1, + }; + const mockCurrentRule3: RuleResponse = { + ...convertPrebuiltRuleAssetToRuleResponse(baseRule), + rule_id: 'rule-3', + revision: 1, + version: 1, + }; + + const result = getUpgradeableRules({ + rawUpgradeableRules: [ + mockUpgradeableRule, + mockUpgradeableRule2, + createUpgradeableRule('rule-3', 1, 2), + ], + currentRules: [mockCurrentRule, mockCurrentRule2, mockCurrentRule3], + versionSpecifiers: [ + mockVersionSpecifier, + { ...mockVersionSpecifier, rule_id: 'rule-2' }, + { ...mockVersionSpecifier, rule_id: 'rule-3', revision: 0 }, + { ...mockVersionSpecifier, rule_id: 'rule-4' }, + { ...mockVersionSpecifier, rule_id: 'rule-5', revision: 1 }, + ], + mode: ModeEnum.SPECIFIC_RULES, + }); + + expect(result.upgradeableRules).toEqual([mockUpgradeableRule, mockUpgradeableRule2]); + expect(result.fetchErrors).toHaveLength(3); + expect(result.fetchErrors[0].error.message).toContain( + 'Revision mismatch for rule_id rule-3: expected 1, got 0' + ); + expect(result.fetchErrors[1].error.message).toContain( + 'Rule with rule_id "rule-4" and version "1" not found' + ); + expect(result.fetchErrors[2].error.message).toContain( + 'Rule with rule_id "rule-5" and version "1" not found' + ); + expect(result.skippedRules).toEqual([]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts new file mode 100644 index 0000000000000..acfdb674c309a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + RuleResponse, + RuleUpgradeSpecifier, + SkippedRuleUpgrade, +} from '../../../../../../common/api/detection_engine'; +import { ModeEnum, SkipRuleUpgradeReasonEnum } from '../../../../../../common/api/detection_engine'; +import type { PromisePoolError } from '../../../../../utils/promise_pool'; +import type { Mode } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; + +export const getUpgradeableRules = ({ + rawUpgradeableRules, + currentRules, + versionSpecifiers, + mode, +}: { + rawUpgradeableRules: RuleTriad[]; + currentRules: RuleResponse[]; + versionSpecifiers?: RuleUpgradeSpecifier[]; + mode: Mode; +}) => { + const upgradeableRules = new Map( + rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) + ); + const fetchErrors: Array<PromisePoolError<{ rule_id: string }, Error>> = []; + const skippedRules: SkippedRuleUpgrade[] = []; + + if (mode === ModeEnum.SPECIFIC_RULES) { + const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); + const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); + versionSpecifiers?.forEach((rule) => { + // Check that the requested rule was found + if (!installedRuleIds.has(rule.rule_id)) { + fetchErrors.push({ + error: new Error( + `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` + ), + item: rule, + }); + return; + } + + // Check that the requested rule is upgradeable + if (!upgradeableRuleIds.has(rule.rule_id)) { + skippedRules.push({ + rule_id: rule.rule_id, + reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, + }); + return; + } + + // Check that rule revisions match (no update slipped in since the user reviewed the list) + const currentRevision = currentRules.find( + (currentRule) => currentRule.rule_id === rule.rule_id + )?.revision; + if (rule.revision !== currentRevision) { + fetchErrors.push({ + error: new Error( + `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` + ), + item: rule, + }); + // Remove the rule from the list of upgradeable rules + if (upgradeableRules.has(rule.rule_id)) { + upgradeableRules.delete(rule.rule_id); + } + } + }); + } + + return { + upgradeableRules: Array.from(upgradeableRules.values()), + fetchErrors, + skippedRules, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts new file mode 100644 index 0000000000000..00de04c291aeb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + PickVersionValues, + PerformRuleUpgradeRequestBody, + AllFieldsDiff, +} from '../../../../../../common/api/detection_engine'; +import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; +import { createFieldUpgradeSpecifier } from './create_field_upgrade_specifier'; +import { mapDiffableRuleFieldValueToRuleSchemaFormat } from './diffable_rule_fields_mappings'; +import { getFieldPredefinedValue } from './get_field_predefined_value'; +import { getValueFromRuleTriad, getValueFromMergedVersion } from './get_value_from_rule_version'; + +interface GetValueForFieldArgs { + fieldName: keyof PrebuiltRuleAsset; + upgradeableRule: RuleTriad; + globalPickVersion: PickVersionValues; + requestBody: PerformRuleUpgradeRequestBody; + ruleFieldsDiff: AllFieldsDiff; +} + +export const getValueForField = ({ + fieldName, + upgradeableRule, + globalPickVersion, + requestBody, + ruleFieldsDiff, +}: GetValueForFieldArgs) => { + const fieldStatus = getFieldPredefinedValue(fieldName, upgradeableRule); + + if (fieldStatus.type === 'PREDEFINED_VALUE') { + return fieldStatus.value; + } + + if (requestBody.mode === 'ALL_RULES') { + return globalPickVersion === 'MERGED' + ? getValueFromMergedVersion({ + fieldName, + upgradeableRule, + fieldUpgradeSpecifier: { + pick_version: globalPickVersion, + }, + ruleFieldsDiff, + }) + : getValueFromRuleTriad({ + fieldName, + upgradeableRule, + fieldUpgradeSpecifier: { + pick_version: globalPickVersion, + }, + }); + } + + // Handle SPECIFIC_RULES mode + const ruleUpgradeSpecifier = requestBody.rules.find( + (r) => r.rule_id === upgradeableRule.target.rule_id + ); + + if (!ruleUpgradeSpecifier) { + throw new Error(`Rule payload for upgradable rule ${upgradeableRule.target.rule_id} not found`); + } + + const fieldUpgradeSpecifier = createFieldUpgradeSpecifier({ + fieldName, + ruleUpgradeSpecifier, + targetRuleType: upgradeableRule.target.type, + globalPickVersion, + }); + + if (fieldUpgradeSpecifier.pick_version === 'RESOLVED') { + const resolvedValue = fieldUpgradeSpecifier.resolved_value; + + return mapDiffableRuleFieldValueToRuleSchemaFormat(fieldName, resolvedValue); + } + + return fieldUpgradeSpecifier.pick_version === 'MERGED' + ? getValueFromMergedVersion({ + fieldName, + upgradeableRule, + fieldUpgradeSpecifier, + ruleFieldsDiff, + }) + : getValueFromRuleTriad({ + fieldName, + upgradeableRule, + fieldUpgradeSpecifier, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts new file mode 100644 index 0000000000000..3bef2ea7c742c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + RuleFieldsToUpgrade, + AllFieldsDiff, +} from '../../../../../../common/api/detection_engine'; +import { RULE_DEFAULTS } from '../../../rule_management/logic/detection_rules_client/mergers/apply_rule_defaults'; +import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; +import { + mapRuleFieldToDiffableRuleField, + mapDiffableRuleFieldValueToRuleSchemaFormat, +} from './diffable_rule_fields_mappings'; + +const RULE_DEFAULTS_FIELDS_SET = new Set(Object.keys(RULE_DEFAULTS)); + +export const getValueFromMergedVersion = ({ + fieldName, + upgradeableRule, + fieldUpgradeSpecifier, + ruleFieldsDiff, +}: { + fieldName: keyof PrebuiltRuleAsset; + upgradeableRule: RuleTriad; + fieldUpgradeSpecifier: NonNullable<RuleFieldsToUpgrade[keyof RuleFieldsToUpgrade]>; + ruleFieldsDiff: AllFieldsDiff; +}) => { + const ruleId = upgradeableRule.target.rule_id; + const diffableRuleFieldName = mapRuleFieldToDiffableRuleField({ + ruleType: upgradeableRule.target.type, + fieldName, + }); + + if (fieldUpgradeSpecifier.pick_version === 'MERGED') { + const ruleFieldDiff = ruleFieldsDiff[diffableRuleFieldName]; + + if (ruleFieldDiff && ruleFieldDiff.conflict !== 'NONE') { + throw new Error( + `Automatic merge calculation for field '${diffableRuleFieldName}' in rule of rule_id ${ruleId} resulted in a conflict. Please resolve the conflict manually or choose another value for 'pick_version'.` + ); + } + + const mergedVersion = ruleFieldDiff.merged_version; + + return mapDiffableRuleFieldValueToRuleSchemaFormat(fieldName, mergedVersion); + } +}; + +export const getValueFromRuleTriad = ({ + fieldName, + upgradeableRule, + fieldUpgradeSpecifier, +}: { + fieldName: keyof PrebuiltRuleAsset; + upgradeableRule: RuleTriad; + fieldUpgradeSpecifier: NonNullable<RuleFieldsToUpgrade[keyof RuleFieldsToUpgrade]>; +}) => { + const ruleId = upgradeableRule.target.rule_id; + const diffableRuleFieldName = mapRuleFieldToDiffableRuleField({ + ruleType: upgradeableRule.target.type, + fieldName, + }); + + const pickVersion = fieldUpgradeSpecifier.pick_version.toLowerCase() as keyof RuleTriad; + + // By this point, can be only 'base', 'current' or 'target' + const ruleVersion = upgradeableRule[pickVersion]; + + if (!ruleVersion) { + // Current and target versions should always be present + // but base version might not; throw if version is missing. + throw new Error( + `Missing '${pickVersion}' version for field '${diffableRuleFieldName}' in rule ${ruleId}` + ); + } + + // No need for conversions in the field names here since the rule versions in + // UpgradableRule have the values in the 'non-grouped' PrebuiltRuleAsset schema format. + const nonResolvedValue = ruleVersion[fieldName]; + + // If there's no value for the field in the rule versions, check if the field + // requires a default value for it. If it does, return the default value. + if (nonResolvedValue === undefined && RULE_DEFAULTS_FIELDS_SET.has(fieldName)) { + return RULE_DEFAULTS[fieldName as keyof typeof RULE_DEFAULTS]; + } + + // Otherwise, return the non-resolved value, which might be undefined. + return nonResolvedValue; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts index f95189d6af34d..085c41db3a5db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -10,16 +10,10 @@ import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { PERFORM_RULE_UPGRADE_URL, PerformRuleUpgradeRequestBody, - PickVersionValuesEnum, - SkipRuleUpgradeReasonEnum, + ModeEnum, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { - PerformRuleUpgradeResponseBody, - SkippedRuleUpgrade, -} from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import { assertUnreachable } from '../../../../../../common/utility_types'; +import type { PerformRuleUpgradeResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { buildSiemResponse } from '../../../routes/utils'; import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors'; import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; @@ -27,9 +21,10 @@ import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { upgradePrebuiltRules } from '../../logic/rule_objects/upgrade_prebuilt_rules'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; -import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; -import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; import { PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS } from '../../constants'; +import { getUpgradeableRules } from './get_upgradeable_rules'; +import { createModifiedPrebuiltRuleAssets } from './create_upgradeable_rules_payload'; +import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -63,108 +58,35 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - const { mode, pick_version: globalPickVersion = PickVersionValuesEnum.TARGET } = - request.body; - - const fetchErrors: Array<PromisePoolError<{ rule_id: string }>> = []; - const targetRules: PrebuiltRuleAsset[] = []; - const skippedRules: SkippedRuleUpgrade[] = []; + const { mode } = request.body; - const versionSpecifiers = mode === 'ALL_RULES' ? undefined : request.body.rules; - const versionSpecifiersMap = new Map( - versionSpecifiers?.map((rule) => [rule.rule_id, rule]) - ); - const ruleVersionsMap = await fetchRuleVersionsTriad({ + const versionSpecifiers = mode === ModeEnum.ALL_RULES ? undefined : request.body.rules; + const ruleTriadsMap = await fetchRuleVersionsTriad({ ruleAssetsClient, ruleObjectsClient, versionSpecifiers, }); - const versionBuckets = getVersionBuckets(ruleVersionsMap); - const { currentRules } = versionBuckets; - // The upgradeable rules list is mutable; we can remove rules from it because of version mismatch - let upgradeableRules = versionBuckets.upgradeableRules; + const ruleGroups = getRuleGroups(ruleTriadsMap); - // Perform all the checks we can before we start the upgrade process - if (mode === 'SPECIFIC_RULES') { - const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); - const upgradeableRuleIds = new Set( - upgradeableRules.map(({ current }) => current.rule_id) - ); - request.body.rules.forEach((rule) => { - // Check that the requested rule was found - if (!installedRuleIds.has(rule.rule_id)) { - fetchErrors.push({ - error: new Error( - `Rule with ID "${rule.rule_id}" and version "${rule.version}" not found` - ), - item: rule, - }); - return; - } - - // Check that the requested rule is upgradeable - if (!upgradeableRuleIds.has(rule.rule_id)) { - skippedRules.push({ - rule_id: rule.rule_id, - reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, - }); - return; - } - - // Check that rule revisions match (no update slipped in since the user reviewed the list) - const currentRevision = ruleVersionsMap.get(rule.rule_id)?.current?.revision; - if (rule.revision !== currentRevision) { - fetchErrors.push({ - error: new Error( - `Revision mismatch for rule ID ${rule.rule_id}: expected ${rule.revision}, got ${currentRevision}` - ), - item: rule, - }); - // Remove the rule from the list of upgradeable rules - upgradeableRules = upgradeableRules.filter( - ({ current }) => current.rule_id !== rule.rule_id - ); - } - }); - } + const { upgradeableRules, skippedRules, fetchErrors } = getUpgradeableRules({ + rawUpgradeableRules: ruleGroups.upgradeableRules, + currentRules: ruleGroups.currentRules, + versionSpecifiers, + mode, + }); - // Construct the list of target rule versions - upgradeableRules.forEach(({ current, target }) => { - const rulePickVersion = - versionSpecifiersMap?.get(current.rule_id)?.pick_version ?? globalPickVersion; - switch (rulePickVersion) { - case PickVersionValuesEnum.BASE: - const baseVersion = ruleVersionsMap.get(current.rule_id)?.base; - if (baseVersion) { - targetRules.push({ ...baseVersion, version: target.version }); - } else { - fetchErrors.push({ - error: new Error(`Could not find base version for rule ${current.rule_id}`), - item: current, - }); - } - break; - case PickVersionValuesEnum.CURRENT: - targetRules.push({ ...current, version: target.version }); - break; - case PickVersionValuesEnum.TARGET: - targetRules.push(target); - break; - case PickVersionValuesEnum.MERGED: - // TODO: Implement functionality to handle MERGED - targetRules.push(target); - break; - default: - assertUnreachable(rulePickVersion); + const { modifiedPrebuiltRuleAssets, processingErrors } = createModifiedPrebuiltRuleAssets( + { + upgradeableRules, + requestBody: request.body, } - }); + ); - // Perform the upgrade const { results: updatedRules, errors: installationErrors } = await upgradePrebuiltRules( detectionRulesClient, - targetRules + modifiedPrebuiltRuleAssets ); - const ruleErrors = [...fetchErrors, ...installationErrors]; + const ruleErrors = [...fetchErrors, ...processingErrors, ...installationErrors]; const { error: timelineInstallationError } = await performTimelinesInstallation( ctx.securitySolution diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts index ec3ca342bf8c9..00fc5e2beb5b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts @@ -17,9 +17,9 @@ import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; -import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; import { PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS } from '../../constants'; +import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const reviewRuleInstallationRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -52,7 +52,7 @@ export const reviewRuleInstallationRoute = (router: SecuritySolutionPluginRouter ruleAssetsClient, ruleObjectsClient, }); - const { installableRules } = getVersionBuckets(ruleVersionsMap); + const { installableRules } = getRuleGroups(ruleVersionsMap); const body: ReviewRuleInstallationResponseBody = { stats: calculateRuleStats(installableRules), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts index 8b229c6406b10..382ec27a1bf35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts @@ -26,9 +26,9 @@ import { calculateRuleDiff } from '../../logic/diff/calculate_rule_diff'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; -import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; import { PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS } from '../../constants'; +import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -61,7 +61,7 @@ export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => ruleAssetsClient, ruleObjectsClient, }); - const { upgradeableRules } = getVersionBuckets(ruleVersionsMap); + const { upgradeableRules } = getRuleGroups(ruleVersionsMap); const ruleDiffCalculationResults = upgradeableRules.map(({ current }) => { const ruleVersions = ruleVersionsMap.get(current.rule_id); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts index 3daaab8ecf10f..0dbfd8a230a5a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts @@ -13,7 +13,7 @@ import { withSecuritySpan } from '../../../../../utils/with_security_span'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; import { validatePrebuiltRuleAssets } from './prebuilt_rule_assets_validation'; import { PREBUILT_RULE_ASSETS_SO_TYPE } from './prebuilt_rule_assets_type'; -import type { RuleVersionSpecifier } from '../../model/rule_versions/rule_version_specifier'; +import type { RuleVersionSpecifier } from '../rule_versions/rule_version_specifier'; const MAX_PREBUILT_RULES_COUNT = 10_000; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts index ae7bdc6b391b4..11a5660e77a31 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts @@ -8,7 +8,7 @@ import type { RuleVersions } from '../diff/calculate_rule_diff'; import type { IPrebuiltRuleAssetsClient } from '../rule_assets/prebuilt_rule_assets_client'; import type { IPrebuiltRuleObjectsClient } from '../rule_objects/prebuilt_rule_objects_client'; -import type { RuleVersionSpecifier } from '../../model/rule_versions/rule_version_specifier'; +import type { RuleVersionSpecifier } from './rule_version_specifier'; import { zipRuleVersions } from './zip_rule_versions'; interface GetRuleVersionsMapArgs { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/rule_version_specifier.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/rule_version_specifier.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/rule_version_specifier.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/rule_version_specifier.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts index 8f9c1a6a32357..6442582c1b573 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts @@ -4,11 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { z } from '@kbn/zod'; +import type { + EqlRuleCreateFields, + QueryRuleCreateFields, + SavedQueryRuleCreateFields, + ThresholdRuleCreateFields, + ThreatMatchRuleCreateFields, + MachineLearningRuleCreateFields, + NewTermsRuleCreateFields, + EsqlRuleCreateFields, + TypeSpecificCreatePropsInternal, +} from '../../../../../../common/api/detection_engine'; +import { PrebuiltRuleAsset, type PrebuiltAssetBaseProps } from './prebuilt_rule_asset'; -import type { PrebuiltRuleAsset } from './prebuilt_rule_asset'; +type TypeSpecificCreateProps = z.infer<typeof TypeSpecificCreatePropsInternal>; -export const getPrebuiltRuleMock = (rewrites?: Partial<PrebuiltRuleAsset>): PrebuiltRuleAsset => - ({ +export const getPrebuiltRuleMock = (rewrites?: Partial<PrebuiltRuleAsset>): PrebuiltRuleAsset => { + return PrebuiltRuleAsset.parse({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -19,40 +32,42 @@ export const getPrebuiltRuleMock = (rewrites?: Partial<PrebuiltRuleAsset>): Preb rule_id: 'rule-1', version: 1, author: [], + license: 'Elastic License v2', ...rewrites, - } as PrebuiltRuleAsset); + }); +}; -export const getPrebuiltRuleWithExceptionsMock = (): PrebuiltRuleAsset => ({ - description: 'A rule with an exception list', - name: 'A rule with an exception list', - query: 'user.name: root or user.name: admin', - severity: 'high', +export const getPrebuiltQueryRuleSpecificFieldsMock = (): QueryRuleCreateFields => ({ type: 'query', - risk_score: 42, + query: 'user.name: root or user.name: admin', language: 'kuery', - rule_id: 'rule-with-exceptions', - exceptions_list: [ - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - version: 2, }); -export const getPrebuiltThreatMatchRuleMock = (): PrebuiltRuleAsset => ({ - description: 'some description', - name: 'Query with a rule id', +export const getPrebuiltEqlRuleSpecificFieldsMock = (): EqlRuleCreateFields => ({ + type: 'eql', + query: 'process where process.name == "cmd.exe"', + language: 'eql', +}); + +export const getPrebuiltSavedQueryRuleSpecificFieldsMock = (): SavedQueryRuleCreateFields => ({ + type: 'saved_query', + saved_id: 'saved-query-id', +}); + +export const getPrebuiltThresholdRuleSpecificFieldsMock = (): ThresholdRuleCreateFields => ({ + type: 'threshold', query: 'user.name: root or user.name: admin', - severity: 'high', + language: 'kuery', + threshold: { + field: 'user.name', + value: 5, + }, +}); + +export const getPrebuiltThreatMatchRuleSpecificFieldsMock = (): ThreatMatchRuleCreateFields => ({ type: 'threat_match', - risk_score: 55, + query: 'user.name: root or user.name: admin', language: 'kuery', - rule_id: 'rule-1', - version: 1, - author: [], threat_query: '*:*', threat_index: ['list-index'], threat_mapping: [ @@ -66,22 +81,115 @@ export const getPrebuiltThreatMatchRuleMock = (): PrebuiltRuleAsset => ({ ], }, ], - threat_filters: [ - { - bool: { - must: [ - { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', - }, - }, - ], - filter: [], - should: [], - must_not: [], - }, - }, - ], + concurrent_searches: 2, + items_per_search: 10, }); + +export const getPrebuiltThreatMatchRuleMock = (): PrebuiltRuleAsset => ({ + description: 'some description', + name: 'Query with a rule id', + severity: 'high', + risk_score: 55, + rule_id: 'rule-1', + version: 1, + author: [], + license: 'Elastic License v2', + ...getPrebuiltThreatMatchRuleSpecificFieldsMock(), +}); + +export const getPrebuiltMachineLearningRuleSpecificFieldsMock = + (): MachineLearningRuleCreateFields => ({ + type: 'machine_learning', + anomaly_threshold: 50, + machine_learning_job_id: 'ml-job-id', + }); + +export const getPrebuiltNewTermsRuleSpecificFieldsMock = (): NewTermsRuleCreateFields => ({ + type: 'new_terms', + query: 'user.name: *', + language: 'kuery', + new_terms_fields: ['user.name'], + history_window_start: '1h', +}); + +export const getPrebuiltEsqlRuleSpecificFieldsMock = (): EsqlRuleCreateFields => ({ + type: 'esql', + query: 'from process where process.name == "cmd.exe"', + language: 'esql', +}); + +export const getPrebuiltRuleMockOfType = <T extends TypeSpecificCreateProps>( + type: T['type'] +): PrebuiltAssetBaseProps & + Extract<TypeSpecificCreateProps, T> & { version: number; rule_id: string } => { + let typeSpecificFields: TypeSpecificCreateProps; + + switch (type) { + case 'query': + typeSpecificFields = getPrebuiltQueryRuleSpecificFieldsMock(); + break; + case 'eql': + typeSpecificFields = getPrebuiltEqlRuleSpecificFieldsMock(); + break; + case 'saved_query': + typeSpecificFields = getPrebuiltSavedQueryRuleSpecificFieldsMock(); + break; + case 'threshold': + typeSpecificFields = getPrebuiltThresholdRuleSpecificFieldsMock(); + break; + case 'threat_match': + typeSpecificFields = getPrebuiltThreatMatchRuleSpecificFieldsMock(); + break; + case 'machine_learning': + typeSpecificFields = getPrebuiltMachineLearningRuleSpecificFieldsMock(); + break; + case 'new_terms': + typeSpecificFields = getPrebuiltNewTermsRuleSpecificFieldsMock(); + break; + case 'esql': + typeSpecificFields = getPrebuiltEsqlRuleSpecificFieldsMock(); + break; + default: + throw new Error(`Unsupported rule type: ${type}`); + } + + return { + tags: ['tag1', 'tag2'], + description: 'some description', + name: `${type} rule`, + severity: 'high', + risk_score: 55, + author: [], + license: 'Elastic License v2', + ...typeSpecificFields, + rule_id: `rule-${type}`, + version: 1, + }; +}; + +export const getPrebuiltRuleWithExceptionsMock = ( + rewrites?: Partial<PrebuiltRuleAsset> +): PrebuiltRuleAsset => { + const parsedFields = rewrites ? PrebuiltRuleAsset.parse(rewrites) : {}; + + return { + description: 'A rule with an exception list', + name: 'A rule with an exception list', + query: 'user.name: root or user.name: admin', + severity: 'high', + type: 'query', + risk_score: 42, + language: 'kuery', + rule_id: 'rule-with-exceptions', + exceptions_list: [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + namespace_type: 'agnostic', + type: 'endpoint', + }, + ], + version: 2, + ...parsedFields, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts index cc7e38632547f..8069ee0385eb7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts @@ -20,7 +20,6 @@ function zodMaskFor<T>() { return Object.assign({}, ...propObjects); }; } - /** * The PrebuiltRuleAsset schema is created based on the rule schema defined in our OpenAPI specs. * However, we don't need all the rule schema fields to be present in the PrebuiltRuleAsset. @@ -39,6 +38,7 @@ const BASE_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET = zodMaskFor<BaseCreateProps>( 'outcome', ]); +export type PrebuiltAssetBaseProps = z.infer<typeof PrebuiltAssetBaseProps>; export const PrebuiltAssetBaseProps = BaseCreateProps.omit( BASE_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET ); @@ -65,31 +65,3 @@ export const PrebuiltRuleAsset = PrebuiltAssetBaseProps.and(TypeSpecificCreatePr version: RuleVersion, }) ); - -function createUpgradableRuleFieldsPayloadByType() { - const baseFields = Object.keys(PrebuiltAssetBaseProps.shape); - - return new Map( - TypeSpecificCreatePropsInternal.options.map((option) => { - const typeName = option.shape.type.value; - const typeSpecificFieldsForType = Object.keys(option.shape); - - return [typeName, [...baseFields, ...typeSpecificFieldsForType]]; - }) - ); -} - -/** - * Map of the fields payloads to be passed to the `upgradePrebuiltRules()` method during the - * Upgrade workflow (`/upgrade/_perform` endpoint) by type. - * - * Creating this Map dynamically, based on BaseCreateProps and TypeSpecificFields, ensures that we don't need to: - * - manually add rule types to this Map if they are created - * - manually add or remove any fields if they are added or removed to a specific rule type - * - manually add or remove any fields if we decide that they should not be part of the upgradable fields. - * - * Notice that this Map includes, for each rule type, all fields that are part of the BaseCreateProps and all fields that - * are part of the TypeSpecificFields, including those that are not part of RuleUpgradeSpecifierFields schema, where - * the user of the /upgrade/_perform endpoint can specify which fields to upgrade during the upgrade workflow. - */ -export const UPGRADABLE_FIELDS_PAYLOAD_BY_RULE_TYPE = createUpgradableRuleFieldsPayloadByType(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_groups/get_rule_groups.ts similarity index 75% rename from x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_groups/get_rule_groups.ts index 0c541c0ae00ff..c9adf6db850fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_groups/get_rule_groups.ts @@ -9,7 +9,21 @@ import type { RuleResponse } from '../../../../../../common/api/detection_engine import type { RuleVersions } from '../../logic/diff/calculate_rule_diff'; import type { PrebuiltRuleAsset } from '../rule_assets/prebuilt_rule_asset'; -export interface VersionBuckets { +export interface RuleTriad { + /** + * The base version of the rule (no customizations) + */ + base?: PrebuiltRuleAsset; + /** + * The currently installed version + */ + current: RuleResponse; + /** + * The latest available version + */ + target: PrebuiltRuleAsset; +} +export interface RuleGroups { /** * Rules that are currently installed in Kibana */ @@ -21,16 +35,7 @@ export interface VersionBuckets { /** * Rules that are installed but outdated */ - upgradeableRules: Array<{ - /** - * The currently installed version - */ - current: RuleResponse; - /** - * The latest available version - */ - target: PrebuiltRuleAsset; - }>; + upgradeableRules: RuleTriad[]; /** * All available rules * (installed and not installed) @@ -38,13 +43,13 @@ export interface VersionBuckets { totalAvailableRules: PrebuiltRuleAsset[]; } -export const getVersionBuckets = (ruleVersionsMap: Map<string, RuleVersions>): VersionBuckets => { +export const getRuleGroups = (ruleVersionsMap: Map<string, RuleVersions>): RuleGroups => { const currentRules: RuleResponse[] = []; const installableRules: PrebuiltRuleAsset[] = []; const totalAvailableRules: PrebuiltRuleAsset[] = []; - const upgradeableRules: VersionBuckets['upgradeableRules'] = []; + const upgradeableRules: RuleGroups['upgradeableRules'] = []; - ruleVersionsMap.forEach(({ current, target }) => { + ruleVersionsMap.forEach(({ base, current, target }) => { if (target != null) { // If this rule is available in the package totalAvailableRules.push(target); @@ -63,6 +68,7 @@ export const getVersionBuckets = (ruleVersionsMap: Map<string, RuleVersions>): V if (current != null && target != null && current.version < target.version) { // If this rule is installed but outdated upgradeableRules.push({ + base, current, target, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts index ba21037ba376f..becc68f3d0075 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts @@ -85,6 +85,8 @@ export const applyRulePatch = async ({ from: rulePatch.from ?? existingRule.from, license: rulePatch.license ?? existingRule.license, output_index: rulePatch.output_index ?? existingRule.output_index, + alias_purpose: rulePatch.alias_purpose ?? existingRule.alias_purpose, + alias_target_id: rulePatch.alias_target_id ?? existingRule.alias_target_id, timeline_id: rulePatch.timeline_id ?? existingRule.timeline_id, timeline_title: rulePatch.timeline_title ?? existingRule.timeline_title, meta: rulePatch.meta ?? existingRule.meta, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts index ee5686e96d130..64486bed14304 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts @@ -14,7 +14,7 @@ import type { PrebuiltRuleAsset } from '../../../../prebuilt_rules'; import type { IPrebuiltRuleAssetsClient } from '../../../../prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; import { convertAlertingRuleToRuleResponse } from '../converters/convert_alerting_rule_to_rule_response'; import { convertRuleResponseToAlertingRule } from '../converters/convert_rule_response_to_alerting_rule'; -import { applyRulePatch } from '../mergers/apply_rule_patch'; +import { applyRuleUpdate } from '../mergers/apply_rule_update'; import { ClientError, validateMlAuth } from '../utils'; import { createRule } from './create_rule'; import { getRuleByRuleId } from './get_rule_by_rule_id'; @@ -68,17 +68,17 @@ export const upgradePrebuiltRule = async ({ return createdRule; } - // Else, simply patch it. - const patchedRule = await applyRulePatch({ + // Else, recreate the rule from scratch with the passed payload. + const updatedRule = await applyRuleUpdate({ prebuiltRuleAssetClient, existingRule, - rulePatch: ruleAsset, + ruleUpdate: ruleAsset, }); - const patchedInternalRule = await rulesClient.update({ + const updatedInternalRule = await rulesClient.update({ id: existingRule.id, - data: convertRuleResponseToAlertingRule(patchedRule, actionsClient), + data: convertRuleResponseToAlertingRule(updatedRule, actionsClient), }); - return convertAlertingRuleToRuleResponse(patchedInternalRule); + return convertAlertingRuleToRuleResponse(updatedInternalRule); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts index 40a967c068a00..93deebb4ad7d9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts @@ -39,6 +39,20 @@ export default ({ getService }: FtrProviderContext) => { describe('@serverless @ess update_actions', () => { describe('updating actions', () => { + before(async () => { + await es.indices.delete({ index: 'logs-test', ignore_unavailable: true }); + await es.indices.create({ + index: 'logs-test', + mappings: { + properties: { + '@timestamp': { + type: 'date', + }, + }, + }, + }); + }); + beforeEach(async () => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts index 3c5806688cd61..03772258bd679 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts @@ -14,7 +14,7 @@ import { createRuleAssetSavedObject, createPrebuiltRuleAssetSavedObjects, installPrebuiltRules, - upgradePrebuiltRules, + performUpgradePrebuiltRules, createHistoricalPrebuiltRuleAssetSavedObjects, getPrebuiltRulesAndTimelinesStatus, installPrebuiltRulesAndTimelines, @@ -136,8 +136,11 @@ export default ({ getService }: FtrProviderContext): void => { // Increment the version of one of the installed rules and create the new rule assets ruleAssetSavedObjects[0]['security-rule'].version += 1; await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - // Upgrade all rules - await upgradePrebuiltRules(es, supertest); + // Upgrade all rules to target version + await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); const { stats } = await getPrebuiltRulesStatus(es, supertest); expect(stats).toMatchObject({ @@ -270,8 +273,11 @@ export default ({ getService }: FtrProviderContext): void => { createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), ]); - // Upgrade the rule - await upgradePrebuiltRules(es, supertest); + // Upgrade the rule to target version + await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); const { stats } = await getPrebuiltRulesStatus(es, supertest); expect(stats).toMatchObject({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts index 72707393c0527..46db3e2602702 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts @@ -17,6 +17,8 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./upgrade_prebuilt_rules')); loadTestFile(require.resolve('./upgrade_prebuilt_rules_with_historical_versions')); loadTestFile(require.resolve('./fleet_integration')); + loadTestFile(require.resolve('./upgrade_perform_prebuilt_rules.all_rules_mode')); + loadTestFile(require.resolve('./upgrade_perform_prebuilt_rules.specific_rules_mode')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.rule_type_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.number_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.single_line_string_fields')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts new file mode 100644 index 0000000000000..2d0fe71e7d5d4 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts @@ -0,0 +1,490 @@ +/* + * 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 type SuperTest from 'supertest'; +import { cloneDeep } from 'lodash'; +import { + QueryRuleCreateFields, + EqlRuleCreateFields, + EsqlRuleCreateFields, + RuleResponse, + ThreatMatchRuleCreateFields, + ThreatMatchRule, + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, + ModeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + createRuleAssetSavedObjectOfType, + installPrebuiltRules, + performUpgradePrebuiltRules, + patchRule, + createHistoricalPrebuiltRuleAssetSavedObjects, + reviewPrebuiltRulesToUpgrade, + getInstalledRules, + createRuleAssetSavedObject, + getWebHookAction, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + const securitySolutionApi = getService('securitySolutionApi'); + + describe('@ess @serverless @skipInServerlessMKI Perform Prebuilt Rules Upgrades - mode: ALL_RULES', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const CURRENT_NAME = 'My current name'; + const CURRENT_TAGS = ['current', 'tags']; + const TARGET_NAME = 'My target name'; + const TARGET_TAGS = ['target', 'tags']; + + describe(`successful updates`, () => { + const queryRule = createRuleAssetSavedObjectOfType<QueryRuleCreateFields>('query'); + const eqlRule = createRuleAssetSavedObjectOfType<EqlRuleCreateFields>('eql'); + const esqlRule = createRuleAssetSavedObjectOfType<EsqlRuleCreateFields>('esql'); + + const basePrebuiltAssets = [queryRule, eqlRule, esqlRule]; + const basePrebuiltAssetsMap = createIdToRuleMap( + basePrebuiltAssets.map((r) => r['security-rule']) + ); + + const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + + return targetObject; + }); + + it('upgrades all upgreadeable rules fields to their BASE versions', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Perform the upgrade, all rules' fields to their BASE versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'BASE', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(3); + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const matchingBaseAsset = basePrebuiltAssetsMap.get(updatedRule.rule_id); + if (!matchingBaseAsset) { + throw new Error(`Could not find matching base asset for rule ${updatedRule.rule_id}`); + } + + // Rule Version should be incremented by 1 + // Rule Name and Tags should match the base asset's values, not the Target asset's values + expect(updatedRule.version).toEqual(matchingBaseAsset.version + 1); + expect(updatedRule.name).toEqual(matchingBaseAsset.name); + expect(updatedRule.tags).toEqual(matchingBaseAsset.tags); + }); + + // Get installed rules + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + for (const [ruleId, installedRule] of installedRulesMap) { + const matchingBaseAsset = basePrebuiltAssetsMap.get(ruleId); + expect(installedRule.name).toEqual(matchingBaseAsset?.name); + expect(installedRule.tags).toEqual(matchingBaseAsset?.tags); + } + }); + + it('upgrades all upgreadeable rules fields to their CURRENT versions', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Patch all 3 installed rules to create a current version for each + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Perform the upgrade, all rules' fields to their CURRENT versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'CURRENT', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(3); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const matchingBaseAsset = basePrebuiltAssetsMap.get(updatedRule.rule_id); + // Rule Version should be incremented by 1 + // Rule Query should match the current's version query + if (matchingBaseAsset) { + expect(updatedRule.version).toEqual(matchingBaseAsset.version + 1); + expect(updatedRule.name).toEqual(CURRENT_NAME); + expect(updatedRule.tags).toEqual(CURRENT_TAGS); + } else { + throw new Error(`Matching base asset not found for rule_id: ${updatedRule.rule_id}`); + } + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + for (const [_, installedRule] of installedRulesMap) { + expect(installedRule.name).toEqual(CURRENT_NAME); + expect(installedRule.tags).toEqual(CURRENT_TAGS); + } + }); + + it('upgrades all upgreadeable rules fields to their TARGET versions', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Patch all 3 installed rules to create a current version for each + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + query: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Perform the upgrade, all rules' fields to their CURRENT versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'TARGET', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(3); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const matchingBaseAsset = basePrebuiltAssetsMap.get(updatedRule.rule_id); + + // Rule Version should be incremented by 1 + // Rule Query should match the current's version query + if (matchingBaseAsset) { + expect(updatedRule.version).toEqual(matchingBaseAsset.version + 1); + expect(updatedRule.name).toEqual(TARGET_NAME); + expect(updatedRule.tags).toEqual(TARGET_TAGS); + } else { + throw new Error(`Matching base asset not found for rule_id: ${updatedRule.rule_id}`); + } + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + for (const [_, installedRule] of installedRulesMap) { + expect(installedRule.name).toEqual(TARGET_NAME); + expect(installedRule.tags).toEqual(TARGET_TAGS); + } + }); + + it('upgrades all upgreadeable rules fields to their MERGED versions', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Call the /upgrade/_review endpoint to save the calculated merged_versions + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const reviewRuleResponseMap = new Map( + reviewResponse.rules.map((upgradeInfo) => [ + upgradeInfo.rule_id, + { + tags: upgradeInfo.diff.fields.tags?.merged_version, + name: upgradeInfo.diff.fields.name?.merged_version, + }, + ]) + ); + + // Perform the upgrade, all rules' fields to their MERGED versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'MERGED', + }); + const updatedRulesMap = createIdToRuleMap(performUpgradeResponse.results.updated); + + // All upgrades should succeed: neither query nor tags should have a merge conflict + expect(performUpgradeResponse.summary.succeeded).toEqual(3); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + for (const [ruleId, installedRule] of installedRulesMap) { + expect(installedRule.name).toEqual(updatedRulesMap.get(ruleId)?.name); + expect(installedRule.name).toEqual(reviewRuleResponseMap.get(ruleId)?.name); + expect(installedRule.tags).toEqual(updatedRulesMap.get(ruleId)?.tags); + expect(installedRule.tags).toEqual(reviewRuleResponseMap.get(ruleId)?.tags); + } + }); + }); + + describe('edge cases and unhappy paths', () => { + const firstQueryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-1', + }); + const secondQueryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-2', + }); + const eqlRule = createRuleAssetSavedObject({ + type: 'eql', + language: 'eql', + rule_id: 'eql-rule', + }); + + const basePrebuiltAssets = [firstQueryRule, eqlRule, secondQueryRule]; + + it('rejects all updates of rules which have a rule type change if the pick_version is not TARGET', async () => { + // Install base prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Mock a rule type change to 'ml' to the first two rules of the basePrebuiltAssets array + const targetMLPrebuiltAssets = basePrebuiltAssets + .slice(0, 2) + .map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + + return { + ...targetObject, + ...createRuleAssetSavedObject({ + rule_id: targetObject['security-rule'].rule_id, + version: targetObject['security-rule'].version + 1, + type: 'machine_learning', + machine_learning_job_id: 'job_id', + anomaly_threshold: 1, + }), + }; + }); + + // Mock an normal update of the rule 'query-rule-2', with NO rule type change + const targetAssetSameTypeUpdate = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-2', + version: 2, + }); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + ...targetMLPrebuiltAssets, + targetAssetSameTypeUpdate, + ]); + + // Perform the upgrade, all rules' fields to their BASE versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'BASE', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); // update of same type + expect(performUpgradeResponse.summary.failed).toEqual(2); // updates with rule type change + + expect(performUpgradeResponse.errors).toHaveLength(2); + performUpgradeResponse.errors.forEach((error) => { + const ruleId = error.rules[0].rule_id; + expect(error.message).toContain( + `Rule update for rule ${ruleId} has a rule type change. All 'pick_version' values for rule must match 'TARGET'` + ); + }); + }); + + it('rejects updates of rules with a pick_version of MERGED which have fields which result in conflicts in the three way diff calculations', async () => { + // Install base prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Patch all 3 installed rules to create a current version for each + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } + + const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + + return targetObject; + }); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Perform the upgrade, all rules' fields to their MERGED versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'MERGED', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(0); // all rules have conflicts + expect(performUpgradeResponse.summary.failed).toEqual(3); // all rules have conflicts + + performUpgradeResponse.errors.forEach((error) => { + const ruleId = error.rules[0].rule_id; + expect(error.message).toContain( + `Merge conflicts found in rule '${ruleId}' for fields: name, tags. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + }); + }); + + it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when upgrading to TARGET version with undefined fields', async () => { + const baseRule = + createRuleAssetSavedObjectOfType<ThreatMatchRuleCreateFields>('threat_match'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); + await installPrebuiltRules(es, supertest); + + const ruleId = baseRule['security-rule'].rule_id; + + const installedBaseRule = ( + await securitySolutionApi.readRule({ + query: { + rule_id: ruleId, + }, + }) + ).body as ThreatMatchRule; + + // Patch the installed rule to set all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION to some defined value + const currentValues: { [key: string]: unknown } = { + enabled: true, + exceptions_list: [ + { + id: 'test-list', + list_id: 'test-list', + type: 'detection', + namespace_type: 'single', + } as const, + ], + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' as const }, + }, + actions: [await createAction(supertest)], + response_actions: [ + { + params: { + command: 'isolate' as const, + comment: 'comment', + }, + action_type_id: '.endpoint' as const, + }, + ], + meta: { some_key: 'some_value' }, + output_index: '.siem-signals-default', + namespace: 'default', + concurrent_searches: 5, + items_per_search: 100, + }; + + await securitySolutionApi.updateRule({ + body: { + ...installedBaseRule, + ...currentValues, + id: undefined, + }, + }); + + // Create a target version with undefined values for these fields + const targetRule = cloneDeep(baseRule); + targetRule['security-rule'].version += 1; + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + // @ts-expect-error + targetRule['security-rule'][field] = undefined; + }); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + + // Perform the upgrade + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'TARGET', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + + // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(upgradedRule[field]).toEqual(currentValues[field]); + }); + + // Verify the installed rule + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === baseRule['security-rule'].rule_id + ) as ThreatMatchRule; + + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(installedRule[field]).toEqual(currentValues[field]); + }); + }); + }); + }); +}; + +function createIdToRuleMap(rules: Array<PrebuiltRuleAsset | RuleResponse>) { + return new Map(rules.map((rule) => [rule.rule_id, rule])); +} + +async function createAction(supertest: SuperTest.Agent) { + const createConnector = async (payload: Record<string, unknown>) => + (await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200)) + .body; + + const createWebHookConnector = () => createConnector(getWebHookAction()); + + const webHookAction = await createWebHookConnector(); + + const defaultRuleAction = { + id: webHookAction.id, + action_type_id: '.webhook' as const, + group: 'default' as const, + params: { + body: '{"test":"a default action"}', + }, + frequency: { + notifyWhen: 'onThrottleInterval' as const, + summary: true, + throttle: '1h' as const, + }, + uuid: 'd487ec3d-05f2-44ad-8a68-11c97dc92202', + }; + + return defaultRuleAction; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts new file mode 100644 index 0000000000000..8c086c46927e7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts @@ -0,0 +1,861 @@ +/* + * 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 type SuperTest from 'supertest'; +import { cloneDeep } from 'lodash'; +import { + QueryRuleCreateFields, + EqlRuleCreateFields, + EsqlRuleCreateFields, + ThreatMatchRuleCreateFields, + RuleResponse, + ModeEnum, + PickVersionValues, + RuleEqlQuery, + EqlRule, + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; +import { ThreatMatchRule } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/rule_schemas.gen'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + createRuleAssetSavedObjectOfType, + installPrebuiltRules, + performUpgradePrebuiltRules, + patchRule, + getInstalledRules, + createHistoricalPrebuiltRuleAssetSavedObjects, + reviewPrebuiltRulesToUpgrade, + createRuleAssetSavedObject, + getWebHookAction, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + const securitySolutionApi = getService('securitySolutionApi'); + + describe('@ess @serverless @skipInServerlessMKI Perform Prebuilt Rules Upgrades - mode: SPECIFIC_RULES', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const CURRENT_NAME = 'My current name'; + const CURRENT_TAGS = ['current', 'tags']; + const TARGET_NAME = 'My target name'; + const TARGET_TAGS = ['target', 'tags']; + + describe('successful updates', () => { + const queryRule = createRuleAssetSavedObjectOfType<QueryRuleCreateFields>('query'); + const eqlRule = createRuleAssetSavedObjectOfType<EqlRuleCreateFields>('eql'); + const esqlRule = createRuleAssetSavedObjectOfType<EsqlRuleCreateFields>('esql'); + + const basePrebuiltAssets = [queryRule, eqlRule, esqlRule]; + + const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + return targetObject; + }); + + it('upgrades specific rules to their BASE versions', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + revision: 0, + version: rule['security-rule'].version + 1, + })); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'BASE', + rules: rulesToUpgrade, + }); + + const expectedResults = basePrebuiltAssets.map((asset) => ({ + rule_id: asset['security-rule'].rule_id, + version: asset['security-rule'].version + 1, + name: asset['security-rule'].name, + tags: asset['security-rule'].tags, + })); + + expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); + expect(updatedRule.version).toEqual(expected?.version); + expect(updatedRule.name).toEqual(expected?.name); + expect(updatedRule.tags).toEqual(expected?.tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + expectedResults.forEach((expected) => { + const installedRule = installedRulesMap.get(expected.rule_id); + expect(installedRule?.name).toEqual(expected.name); + expect(installedRule?.tags).toEqual(expected.tags); + }); + }); + + it('upgrades specific rules to their CURRENT versions', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + revision: 1, + version: rule['security-rule'].version + 1, + })); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'CURRENT', + rules: rulesToUpgrade, + }); + + const expectedResults = basePrebuiltAssets.map((asset) => ({ + rule_id: asset['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + })); + + expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); + expect(updatedRule.name).toEqual(expected?.name); + expect(updatedRule.tags).toEqual(expected?.tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + expectedResults.forEach((expected) => { + const installedRule = installedRulesMap.get(expected.rule_id); + expect(installedRule?.name).toEqual(expected.name); + expect(installedRule?.tags).toEqual(expected.tags); + }); + }); + + it('upgrades specific rules to their TARGET versions', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + revision: 0, + version: rule['security-rule'].version + 1, + })); + + const expectedResults = basePrebuiltAssets.map((asset) => ({ + rule_id: asset['security-rule'].rule_id, + name: TARGET_NAME, + tags: TARGET_TAGS, + })); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'TARGET', + rules: rulesToUpgrade, + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); + expect(updatedRule.name).toEqual(expected?.name); + expect(updatedRule.tags).toEqual(expected?.tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + expectedResults.forEach((expected) => { + const installedRule = installedRulesMap.get(expected.rule_id); + expect(installedRule?.name).toEqual(expected.name); + expect(installedRule?.tags).toEqual(expected.tags); + }); + }); + + it('upgrades specific rules to their MERGED versions', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const expectedResults = reviewResponse.rules.map((upgradeInfo) => ({ + rule_id: upgradeInfo.rule_id, + name: upgradeInfo.diff.fields.name?.merged_version, + tags: upgradeInfo.diff.fields.tags?.merged_version, + })); + + const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + revision: 0, + version: rule['security-rule'].version + 1, + })); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'MERGED', + rules: rulesToUpgrade, + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); + expect(updatedRule.name).toEqual(expected?.name); + expect(updatedRule.tags).toEqual(expected?.tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + expectedResults.forEach((expected) => { + const installedRule = installedRulesMap.get(expected.rule_id); + expect(installedRule?.name).toEqual(expected.name); + expect(installedRule?.tags).toEqual(expected.tags); + }); + }); + + it('upgrades specific rules to their TARGET versions but overrides some fields with `fields` in the request payload', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + revision: 0, + version: rule['security-rule'].version + 1, + fields: { + name: { pick_version: 'BASE' as PickVersionValues }, + }, + })); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'TARGET', + rules: rulesToUpgrade, + }); + + const expectedResults = basePrebuiltAssets.map((asset) => ({ + rule_id: asset['security-rule'].rule_id, + name: asset['security-rule'].name, + tags: TARGET_TAGS, + })); + + expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); + + performUpgradeResponse.results.updated.forEach((updatedRule) => { + const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); + expect(updatedRule.name).toEqual(expected?.name); + expect(updatedRule.tags).toEqual(expected?.tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + expectedResults.forEach((expected) => { + const installedRule = installedRulesMap.get(expected.rule_id); + expect(installedRule?.name).toEqual(expected.name); + expect(installedRule?.tags).toEqual(expected.tags); + }); + }); + + it('upgrades specific rules with different pick_version at global, rule, and field levels', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + const rulesToUpgrade = [ + { + rule_id: basePrebuiltAssets[0]['security-rule'].rule_id, + revision: 1, + version: basePrebuiltAssets[0]['security-rule'].version + 1, + pick_version: 'CURRENT' as PickVersionValues, + }, + { + rule_id: basePrebuiltAssets[1]['security-rule'].rule_id, + revision: 1, + version: basePrebuiltAssets[1]['security-rule'].version + 1, + fields: { + name: { pick_version: 'TARGET' as PickVersionValues }, + tags: { pick_version: 'BASE' as PickVersionValues }, + }, + }, + { + rule_id: basePrebuiltAssets[2]['security-rule'].rule_id, + revision: 1, + version: basePrebuiltAssets[2]['security-rule'].version + 1, + }, + ]; + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'BASE', + rules: rulesToUpgrade, + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(3); + const updatedRulesMap = createIdToRuleMap(performUpgradeResponse.results.updated); + + const expectedResults = [ + { name: CURRENT_NAME, tags: CURRENT_TAGS }, + { name: TARGET_NAME, tags: basePrebuiltAssets[1]['security-rule'].tags }, + { + name: basePrebuiltAssets[2]['security-rule'].name, + tags: basePrebuiltAssets[2]['security-rule'].tags, + }, + ]; + + basePrebuiltAssets.forEach((asset, index) => { + const ruleId = asset['security-rule'].rule_id; + const updatedRule = updatedRulesMap.get(ruleId); + expect(updatedRule?.name).toEqual(expectedResults[index].name); + expect(updatedRule?.tags).toEqual(expectedResults[index].tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + basePrebuiltAssets.forEach((asset, index) => { + const ruleId = asset['security-rule'].rule_id; + const installedRule = installedRulesMap.get(ruleId); + expect(installedRule?.name).toEqual(expectedResults[index].name); + expect(installedRule?.tags).toEqual(expectedResults[index].tags); + }); + }); + + it('successfully resolves a non-resolvable conflict by using pick_version:RESOLVED for that field', async () => { + const baseEqlRule = createRuleAssetSavedObjectOfType<EqlRuleCreateFields>('eql'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseEqlRule]); + await installPrebuiltRules(es, supertest); + + // Patch the installed rule to edit its query + const patchedQuery = 'sequence by process.name [MY CURRENT QUERY]'; + await patchRule(supertest, log, { + rule_id: baseEqlRule['security-rule'].rule_id, + query: patchedQuery, + }); + + // Create a new version of the prebuilt rule asset with a different query and generate the conflict + const targetEqlRule = cloneDeep(baseEqlRule); + targetEqlRule['security-rule'].version += 1; + targetEqlRule['security-rule'].query = 'sequence by process.name [MY TARGET QUERY]'; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetEqlRule]); + + const resolvedValue = { + query: 'sequence by process.name [MY RESOLVED QUERY]', + language: 'eql', + filters: [], + }; + + // Perform the upgrade with manual conflict resolution + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'MERGED', + rules: [ + { + rule_id: baseEqlRule['security-rule'].rule_id, + revision: 1, + version: baseEqlRule['security-rule'].version + 1, + fields: { + eql_query: { + pick_version: 'RESOLVED', + resolved_value: resolvedValue as RuleEqlQuery, + }, + }, + }, + ], + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + const updatedRule = performUpgradeResponse.results.updated[0] as EqlRule; + expect(updatedRule.rule_id).toEqual(baseEqlRule['security-rule'].rule_id); + expect(updatedRule.query).toEqual(resolvedValue.query); + expect(updatedRule.filters).toEqual(resolvedValue.filters); + expect(updatedRule.language).toEqual(resolvedValue.language); + + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === baseEqlRule['security-rule'].rule_id + ) as EqlRule; + expect(installedRule?.query).toEqual(resolvedValue.query); + expect(installedRule?.filters).toEqual(resolvedValue.filters); + expect(installedRule?.language).toEqual(resolvedValue.language); + }); + }); + + describe('edge cases and unhappy paths', () => { + const queryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule', + }); + const eqlRule = createRuleAssetSavedObject({ + type: 'eql', + language: 'eql', + rule_id: 'eql-rule', + }); + + const basePrebuiltAssets = [queryRule, eqlRule]; + + it('rejects updates when rule type changes and pick_version is not TARGET at all levels', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + const targetMLRule = createRuleAssetSavedObject({ + rule_id: queryRule['security-rule'].rule_id, + version: queryRule['security-rule'].version + 1, + type: 'machine_learning', + machine_learning_job_id: 'job_id', + anomaly_threshold: 1, + }); + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetMLRule]); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'BASE', + rules: [ + { + rule_id: queryRule['security-rule'].rule_id, + revision: 0, + version: queryRule['security-rule'].version + 1, + }, + ], + }); + + expect(performUpgradeResponse.summary.failed).toEqual(1); + expect(performUpgradeResponse.errors[0].message).toContain( + 'Rule update for rule query-rule has a rule type change' + ); + }); + + it('rejects updates when incompatible fields are provided for a rule type', async () => { + const baseEqlRule = createRuleAssetSavedObjectOfType<EqlRuleCreateFields>('eql'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseEqlRule]); + await installPrebuiltRules(es, supertest); + + // Create a new version of the prebuilt rule asset with a different query and generate the conflict + const targetEqlRule = cloneDeep(baseEqlRule); + targetEqlRule['security-rule'].version += 1; + targetEqlRule['security-rule'].query = 'sequence by process.name [MY TARGET QUERY]'; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetEqlRule]); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'TARGET', + rules: [ + { + rule_id: baseEqlRule['security-rule'].rule_id, + revision: 0, + version: baseEqlRule['security-rule'].version + 1, + fields: { + machine_learning_job_id: { pick_version: 'TARGET' }, + }, + }, + ], + }); + + expect(performUpgradeResponse.summary.failed).toEqual(1); + expect(performUpgradeResponse.errors[0].message).toContain( + "machine_learning_job_id is not a valid upgradeable field for type 'eql'" + ); + }); + + it('rejects updates with NON_SOLVABLE conflicts when using MERGED pick_version', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + await patchRule(supertest, log, { + rule_id: queryRule['security-rule'].rule_id, + name: CURRENT_NAME, + }); + + const targetQueryRule = cloneDeep(queryRule); + targetQueryRule['security-rule'].version += 1; + targetQueryRule['security-rule'].name = TARGET_NAME; + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetQueryRule]); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'MERGED', + rules: [ + { + rule_id: queryRule['security-rule'].rule_id, + revision: 1, + version: queryRule['security-rule'].version + 1, + }, + ], + }); + + expect(performUpgradeResponse.summary.failed).toEqual(1); + expect(performUpgradeResponse.errors[0].message).toContain( + `Automatic merge calculation for field 'name' in rule of rule_id ${performUpgradeResponse.errors[0].rules[0].rule_id} resulted in a conflict. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + }); + + it('allows updates with NON_SOLVABLE conflicts when specific fields have non-MERGED pick_version', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + await patchRule(supertest, log, { + rule_id: queryRule['security-rule'].rule_id, + name: CURRENT_NAME, + }); + + const targetQueryRule = cloneDeep(queryRule); + targetQueryRule['security-rule'].version += 1; + targetQueryRule['security-rule'].name = TARGET_NAME; + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetQueryRule]); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'MERGED', + rules: [ + { + rule_id: queryRule['security-rule'].rule_id, + revision: 1, + version: queryRule['security-rule'].version + 1, + fields: { + name: { pick_version: 'TARGET' }, + }, + }, + ], + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + expect(performUpgradeResponse.results.updated[0].name).toEqual(TARGET_NAME); + + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === queryRule['security-rule'].rule_id + ); + expect(installedRule?.name).toEqual(TARGET_NAME); + }); + + it('rejects updates for specific fields with MERGED pick_version and NON_SOLVABLE conflicts', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + await patchRule(supertest, log, { + rule_id: queryRule['security-rule'].rule_id, + name: CURRENT_NAME, + }); + + const targetQueryRule = cloneDeep(queryRule); + targetQueryRule['security-rule'].version += 1; + targetQueryRule['security-rule'].name = TARGET_NAME; + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetQueryRule]); + + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'TARGET', + rules: [ + { + rule_id: queryRule['security-rule'].rule_id, + revision: 1, + version: queryRule['security-rule'].version + 1, + fields: { + name: { pick_version: 'MERGED' }, + }, + }, + ], + }); + + expect(performUpgradeResponse.summary.failed).toEqual(1); + expect(performUpgradeResponse.errors[0].message).toContain( + `Automatic merge calculation for field 'name' in rule of rule_id ${performUpgradeResponse.errors[0].rules[0].rule_id} resulted in a conflict. Please resolve the conflict manually or choose another value for 'pick_version'.` + ); + }); + + it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when upgrading to TARGET version with undefined fields', async () => { + const baseRule = + createRuleAssetSavedObjectOfType<ThreatMatchRuleCreateFields>('threat_match'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); + await installPrebuiltRules(es, supertest); + + const ruleId = baseRule['security-rule'].rule_id; + + const installedBaseRule = ( + await securitySolutionApi.readRule({ + query: { + rule_id: ruleId, + }, + }) + ).body as ThreatMatchRule; + + // Patch the installed rule to set all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION to some defined value + const currentValues: { [key: string]: unknown } = { + enabled: true, + exceptions_list: [ + { + id: 'test-list', + list_id: 'test-list', + type: 'detection', + namespace_type: 'single', + } as const, + ], + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' as const }, + }, + actions: [await createAction(supertest)], + response_actions: [ + { + params: { + command: 'isolate' as const, + comment: 'comment', + }, + action_type_id: '.endpoint' as const, + }, + ], + meta: { some_key: 'some_value' }, + output_index: '.siem-signals-default', + namespace: 'default', + concurrent_searches: 5, + items_per_search: 100, + }; + + await securitySolutionApi.updateRule({ + body: { + ...installedBaseRule, + ...currentValues, + id: undefined, + }, + }); + + // Create a target version with undefined values for these fields + const targetRule = cloneDeep(baseRule); + targetRule['security-rule'].version += 1; + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + // @ts-expect-error + targetRule['security-rule'][field] = undefined; + }); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + + // Perform the upgrade + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'TARGET', + rules: [ + { + rule_id: baseRule['security-rule'].rule_id, + revision: 1, + version: baseRule['security-rule'].version + 1, + }, + ], + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + + // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(upgradedRule[field]).toEqual(currentValues[field]); + }); + + // Verify the installed rule + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === baseRule['security-rule'].rule_id + ) as ThreatMatchRule; + + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(installedRule[field]).toEqual(currentValues[field]); + }); + }); + + it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when fields are attempted to be updated via resolved values', async () => { + const baseRule = + createRuleAssetSavedObjectOfType<ThreatMatchRuleCreateFields>('threat_match'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); + await installPrebuiltRules(es, supertest); + + const ruleId = baseRule['security-rule'].rule_id; + + const installedBaseRule = ( + await securitySolutionApi.readRule({ + query: { + rule_id: ruleId, + }, + }) + ).body as ThreatMatchRule; + + // Set current values for FIELDS_TO_UPGRADE_TO_CURRENT_VERSION + const currentValues: { [key: string]: unknown } = { + enabled: true, + exceptions_list: [ + { + id: 'test-list', + list_id: 'test-list', + type: 'detection', + namespace_type: 'single', + } as const, + ], + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' as const }, + }, + actions: [await createAction(supertest)], + response_actions: [ + { + params: { + command: 'isolate' as const, + comment: 'comment', + }, + action_type_id: '.endpoint' as const, + }, + ], + meta: { some_key: 'some_value' }, + output_index: '.siem-signals-default', + namespace: 'default', + concurrent_searches: 5, + items_per_search: 100, + }; + + await securitySolutionApi.updateRule({ + body: { + ...installedBaseRule, + ...currentValues, + id: undefined, + }, + }); + + // Create a target version with undefined values for these fields + const targetRule = cloneDeep(baseRule); + targetRule['security-rule'].version += 1; + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + // @ts-expect-error + targetRule['security-rule'][field] = undefined; + }); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + + // Create resolved values different from current values + const resolvedValues: { [key: string]: unknown } = { + exceptions_list: [], + alert_suppression: { + group_by: ['test'], + duration: { value: 10, unit: 'm' as const }, + }, + }; + + const fields = Object.fromEntries( + Object.keys(resolvedValues).map((field) => [ + field, + { + pick_version: 'RESOLVED' as PickVersionValues, + resolved_value: resolvedValues[field], + }, + ]) + ); + + // Perform the upgrade with resolved values + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'TARGET', + rules: [ + { + rule_id: baseRule['security-rule'].rule_id, + revision: 1, + version: baseRule['security-rule'].version + 1, + fields, + }, + ], + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + + // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(upgradedRule[field]).toEqual(currentValues[field]); + }); + + // Verify the installed rule + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === baseRule['security-rule'].rule_id + ) as ThreatMatchRule; + + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(installedRule[field]).toEqual(currentValues[field]); + }); + }); + }); + }); +}; + +function createIdToRuleMap(rules: Array<PrebuiltRuleAsset | RuleResponse>) { + return new Map(rules.map((rule) => [rule.rule_id, rule])); +} + +async function createAction(supertest: SuperTest.Agent) { + const createConnector = async (payload: Record<string, unknown>) => + (await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200)) + .body; + + const createWebHookConnector = () => createConnector(getWebHookAction()); + + const webHookAction = await createWebHookConnector(); + + const defaultRuleAction = { + id: webHookAction.id, + action_type_id: '.webhook' as const, + group: 'default' as const, + params: { + body: '{"test":"a default action"}', + }, + frequency: { + notifyWhen: 'onThrottleInterval' as const, + summary: true, + throttle: '1h' as const, + }, + uuid: 'd487ec3d-05f2-44ad-8a68-11c97dc92202', + }; + + return defaultRuleAction; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts index cd336f91fae13..a23ddf40979f6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts @@ -16,7 +16,7 @@ import { getPrebuiltRulesAndTimelinesStatus, getPrebuiltRulesStatus, installPrebuiltRules, - upgradePrebuiltRules, + performUpgradePrebuiltRules, fetchRule, patchRule, } from '../../../../utils'; @@ -100,7 +100,10 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await upgradePrebuiltRules(es, supertest); + const response = await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(response.summary.succeeded).toBe(1); expect(response.summary.skipped).toBe(0); }); @@ -121,7 +124,10 @@ export default ({ getService }: FtrProviderContext): void => { expect(installResponse.summary.skipped).toBe(0); // Call the upgrade prebuilt rules endpoint and check that no rules were updated - const upgradeResponse = await upgradePrebuiltRules(es, supertest); + const upgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(upgradeResponse.summary.succeeded).toBe(0); expect(upgradeResponse.summary.skipped).toBe(0); }); @@ -178,7 +184,10 @@ export default ({ getService }: FtrProviderContext): void => { ]); // Upgrade to a newer version with the same type - await upgradePrebuiltRules(es, supertest); + await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(await fetchRule(supertest, { ruleId: 'rule-to-test-1' })).toMatchObject({ id: initialRuleSoId, @@ -186,8 +195,7 @@ export default ({ getService }: FtrProviderContext): void => { enabled: false, actions, exceptions_list: exceptionsList, - timeline_id: 'some-timeline-id', - timeline_title: 'Some timeline title', + // current values for timeline_id and timeline_title are lost when updating to TARGET version }); }); }); @@ -250,7 +258,10 @@ export default ({ getService }: FtrProviderContext): void => { ]); // Upgrade to a newer version with a different rule type - await upgradePrebuiltRules(es, supertest); + await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(await fetchRule(supertest, { ruleId: 'rule-to-test-2' })).toMatchObject({ id: initialRuleSoId, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts index 049ae3a5a6fd8..0eb37b1112f27 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts @@ -15,7 +15,7 @@ import { createHistoricalPrebuiltRuleAssetSavedObjects, getPrebuiltRulesStatus, installPrebuiltRules, - upgradePrebuiltRules, + performUpgradePrebuiltRules, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; @@ -110,7 +110,10 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated - const response = await upgradePrebuiltRules(es, supertest); + const response = await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); @@ -138,7 +141,10 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated - const response = await upgradePrebuiltRules(es, supertest); + const response = await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts index 8e26b089a9f80..b551d793406ce 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts @@ -19,7 +19,7 @@ import { getPrebuiltRulesStatus, installPrebuiltRules, installPrebuiltRulesPackageByVersion, - upgradePrebuiltRules, + performUpgradePrebuiltRules, reviewPrebuiltRulesToInstall, reviewPrebuiltRulesToUpgrade, } from '../../../../utils'; @@ -227,12 +227,13 @@ export default ({ getService }: FtrProviderContext): void => { prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation.stats.num_rules_to_upgrade_total ).toBe(statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade); - // Call the upgrade _perform endpoint and verify that the number of upgraded rules is the same as the one - // returned by the _review endpoint and the status endpoint - const upgradePrebuiltRulesResponseAfterLatestPackageInstallation = await upgradePrebuiltRules( - es, - supertest - ); + // Call the upgrade _perform endpoint to upgrade all rules to their target version and verify that the number + // of upgraded rules is the same as the one returned by the _review endpoint and the status endpoint + const upgradePrebuiltRulesResponseAfterLatestPackageInstallation = + await performUpgradePrebuiltRules(es, supertest, { + mode: 'ALL_RULES', + pick_version: 'TARGET', + }); expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts index df35d2c439757..8ecb591272492 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts @@ -411,6 +411,7 @@ function expectToMatchRuleSchema(obj: RuleResponse): void { severity: expect.any(String), output_index: expect.any(String), author: expect.arrayContaining([]), + license: expect.any(String), false_positives: expect.arrayContaining([]), from: expect.any(String), max_signals: expect.any(Number), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts index a5c5fe00ed700..a27f99b6f75e8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts @@ -30,6 +30,7 @@ export function getCustomQueryRuleParams( interval: '100m', from: 'now-6m', author: [], + license: 'Elastic License v2', enabled: false, ...rewrites, }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts index 20a8e6cf17280..3ebd928123cc4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts @@ -9,11 +9,21 @@ import { Client } from '@elastic/elasticsearch'; import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; import { getPrebuiltRuleMock, + getPrebuiltRuleMockOfType, getPrebuiltRuleWithExceptionsMock, } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; +import type { TypeSpecificCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +const ruleAssetSavedObjectESFields = { + type: 'security-rule', + references: [], + coreMigrationVersion: '8.6.0', + updated_at: '2022-11-01T12:56:39.717Z', + created_at: '2022-11-01T12:56:39.717Z', +}; + /** * A helper function to create a rule asset saved object * @@ -22,11 +32,20 @@ import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-se */ export const createRuleAssetSavedObject = (overrideParams: Partial<PrebuiltRuleAsset>) => ({ 'security-rule': getPrebuiltRuleMock(overrideParams), - type: 'security-rule', - references: [], - coreMigrationVersion: '8.6.0', - updated_at: '2022-11-01T12:56:39.717Z', - created_at: '2022-11-01T12:56:39.717Z', + ...ruleAssetSavedObjectESFields, +}); + +/** + * A helper function to create a rule asset saved object + * + * @param overrideParams Params to override the default mock + * @returns Created rule asset saved object + */ +export const createRuleAssetSavedObjectOfType = <T extends TypeSpecificCreateProps>( + type: T['type'] +) => ({ + 'security-rule': getPrebuiltRuleMockOfType<T>(type), + ...ruleAssetSavedObjectESFields, }); export const SAMPLE_PREBUILT_RULES = [ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts index fbf9ab7b36384..fabd3df2f2d16 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts @@ -19,4 +19,4 @@ export * from './install_prebuilt_rules_fleet_package'; export * from './install_prebuilt_rules'; export * from './review_install_prebuilt_rules'; export * from './review_upgrade_prebuilt_rules'; -export * from './upgrade_prebuilt_rules'; +export * from './perform_upgrade_prebuilt_rules'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/upgrade_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/perform_upgrade_prebuilt_rules.ts similarity index 67% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/upgrade_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/perform_upgrade_prebuilt_rules.ts index f12d0adbc65f3..c9b2543d61d69 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/upgrade_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/perform_upgrade_prebuilt_rules.ts @@ -7,8 +7,8 @@ import { PERFORM_RULE_UPGRADE_URL, - RuleVersionSpecifier, PerformRuleUpgradeResponseBody, + PerformRuleUpgradeRequestBody, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; @@ -17,30 +17,21 @@ import { refreshSavedObjectIndices } from '../../refresh_index'; /** * Upgrades available prebuilt rules in Kibana. * - * - Pass in an array of rule version specifiers to upgrade specific rules. Otherwise - * all available rules will be upgraded. - * * @param supertest SuperTest instance - * @param rules Array of rule version specifiers to upgrade (optional) + * @param pazload Array of rule version specifiers to upgrade (optional) * @returns Upgrade prebuilt rules response */ -export const upgradePrebuiltRules = async ( +export const performUpgradePrebuiltRules = async ( es: Client, supertest: SuperTest.Agent, - rules?: RuleVersionSpecifier[] + requestBody: PerformRuleUpgradeRequestBody ): Promise<PerformRuleUpgradeResponseBody> => { - let payload = {}; - if (rules) { - payload = { mode: 'SPECIFIC_RULES', rules, pick_version: 'TARGET' }; - } else { - payload = { mode: 'ALL_RULES', pick_version: 'TARGET' }; - } const response = await supertest .post(PERFORM_RULE_UPGRADE_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') .set('x-elastic-internal-origin', 'foo') - .send(payload) + .send(requestBody) .expect(200); await refreshSavedObjectIndices(es); From d85b51db222f29efbd2d8f32067a13b4932feba8 Mon Sep 17 00:00:00 2001 From: Philippe Oberti <philippe.oberti@elastic.co> Date: Wed, 16 Oct 2024 04:42:23 +0200 Subject: [PATCH 81/84] [Security Solution][Notes] - allow filtering by user (#195519) --- .../output/kibana.serverless.staging.yaml | 5 ++ oas_docs/output/kibana.serverless.yaml | 5 ++ oas_docs/output/kibana.staging.yaml | 5 ++ oas_docs/output/kibana.yaml | 5 ++ .../timeline/get_notes/get_notes_route.gen.ts | 1 + .../get_notes/get_notes_route.schema.yaml | 5 ++ ...imeline_api_2023_10_31.bundled.schema.yaml | 5 ++ ...imeline_api_2023_10_31.bundled.schema.yaml | 5 ++ .../public/common/mock/global_state.ts | 1 + .../security_solution/public/notes/api/api.ts | 3 + .../public/notes/components/add_note.tsx | 2 +- .../notes/components/search_row.test.tsx | 65 ++++++++++++++++ .../public/notes/components/search_row.tsx | 73 +++++++++++------- .../public/notes/components/test_ids.ts | 2 + .../public/notes/components/utility_bar.tsx | 13 +++- .../notes/pages/note_management_page.tsx | 14 +++- .../public/notes/store/notes.slice.test.ts | 24 +++++- .../public/notes/store/notes.slice.ts | 74 ++++++++++++------- .../lib/timeline/routes/notes/get_notes.ts | 17 +++++ .../trial_license_complete_tier/helpers.ts | 14 ++-- .../trial_license_complete_tier/notes.ts | 35 ++++++++- 21 files changed, 309 insertions(+), 64 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/notes/components/search_row.test.tsx diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index 6df65e8ae2e3e..d7b1b6d02323a 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -35261,6 +35261,11 @@ paths: schema: nullable: true type: string + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': content: diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 6df65e8ae2e3e..d7b1b6d02323a 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -35261,6 +35261,11 @@ paths: schema: nullable: true type: string + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': content: diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 76e217fcba16d..24b0462ae93ef 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -38692,6 +38692,11 @@ paths: schema: nullable: true type: string + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': content: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 76e217fcba16d..24b0462ae93ef 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -38692,6 +38692,11 @@ paths: schema: nullable: true type: string + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts index c4c48022f6512..a4659d8d98d5a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts @@ -40,6 +40,7 @@ export const GetNotesRequestQuery = z.object({ sortField: z.string().nullable().optional(), sortOrder: z.string().nullable().optional(), filter: z.string().nullable().optional(), + userFilter: z.string().nullable().optional(), }); export type GetNotesRequestQueryInput = z.input<typeof GetNotesRequestQuery>; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml index 985e7728b7cc8..cc8681c6f8f64 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml @@ -51,6 +51,11 @@ paths: schema: type: string nullable: true + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': description: Indicates the requested notes were returned. diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 48eb959168856..8de192ce26826 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -97,6 +97,11 @@ paths: schema: nullable: true type: string + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': content: diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 343ec3dc30a73..66127d5b8cd52 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -97,6 +97,11 @@ paths: schema: nullable: true type: string + - in: query + name: userFilter + schema: + nullable: true + type: string responses: '200': content: diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 16e1e7edf0eaa..01eec48ed7718 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -549,6 +549,7 @@ export const mockGlobalState: State = { direction: 'desc' as const, }, filter: '', + userFilter: '', search: '', selectedIds: [], pendingDeleteIds: [], diff --git a/x-pack/plugins/security_solution/public/notes/api/api.ts b/x-pack/plugins/security_solution/public/notes/api/api.ts index 4bda803950b84..3bac1a0a2d7df 100644 --- a/x-pack/plugins/security_solution/public/notes/api/api.ts +++ b/x-pack/plugins/security_solution/public/notes/api/api.ts @@ -42,6 +42,7 @@ export const fetchNotes = async ({ sortField, sortOrder, filter, + userFilter, search, }: { page: number; @@ -49,6 +50,7 @@ export const fetchNotes = async ({ sortField: string; sortOrder: string; filter: string; + userFilter: string; search: string; }) => { const response = await KibanaServices.get().http.get<GetNotesResponse>(NOTE_URL, { @@ -58,6 +60,7 @@ export const fetchNotes = async ({ sortField, sortOrder, filter, + userFilter, search, }, version: '2023-10-31', diff --git a/x-pack/plugins/security_solution/public/notes/components/add_note.tsx b/x-pack/plugins/security_solution/public/notes/components/add_note.tsx index b3b226550b66f..78a84064467f6 100644 --- a/x-pack/plugins/security_solution/public/notes/components/add_note.tsx +++ b/x-pack/plugins/security_solution/public/notes/components/add_note.tsx @@ -88,7 +88,7 @@ export const AddNote = memo( createNote({ note: { timelineId: timelineId || '', - eventId, + eventId: eventId || '', note: editorValue, }, }) diff --git a/x-pack/plugins/security_solution/public/notes/components/search_row.test.tsx b/x-pack/plugins/security_solution/public/notes/components/search_row.test.tsx new file mode 100644 index 0000000000000..71693edb81724 --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/components/search_row.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { SearchRow } from './search_row'; +import { SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids'; +import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users'; + +jest.mock('../../common/components/user_profiles/use_suggest_users'); + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); + +describe('SearchRow', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: [{ user: { username: 'test' } }, { user: { username: 'elastic' } }], + }); + }); + + it('should render the component', () => { + const { getByTestId } = render(<SearchRow />); + + expect(getByTestId(SEARCH_BAR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(USER_SELECT_TEST_ID)).toBeInTheDocument(); + }); + + it('should call the correct action when entering a value in the search bar', async () => { + const { getByTestId } = render(<SearchRow />); + + const searchBox = getByTestId(SEARCH_BAR_TEST_ID); + + await userEvent.type(searchBox, 'test'); + await userEvent.keyboard('{enter}'); + + expect(mockDispatch).toHaveBeenCalled(); + }); + + it('should call the correct action when select a user', async () => { + const { getByTestId } = render(<SearchRow />); + + const userSelect = getByTestId('comboBoxSearchInput'); + userSelect.focus(); + + const option = await screen.findByText('test'); + fireEvent.click(option); + + expect(mockDispatch).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/notes/components/search_row.tsx b/x-pack/plugins/security_solution/public/notes/components/search_row.tsx index 6e08251a61135..9a33c84cbec58 100644 --- a/x-pack/plugins/security_solution/public/notes/components/search_row.tsx +++ b/x-pack/plugins/security_solution/public/notes/components/search_row.tsx @@ -5,25 +5,19 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSearchBar } from '@elastic/eui'; -import React, { useMemo, useCallback } from 'react'; +import { EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiSearchBar } from '@elastic/eui'; +import React, { useMemo, useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; -import styled from 'styled-components'; -import { userSearchedNotes } from '..'; +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import { i18n } from '@kbn/i18n'; +import type { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types'; +import { SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids'; +import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users'; +import { userFilterUsers, userSearchedNotes } from '..'; -const SearchRowContainer = styled.div` - &:not(:last-child) { - margin-bottom: ${(props) => props.theme.eui.euiSizeL}; - } -`; - -SearchRowContainer.displayName = 'SearchRowContainer'; - -const SearchRowFlexGroup = styled(EuiFlexGroup)` - margin-bottom: ${(props) => props.theme.eui.euiSizeXS}; -`; - -SearchRowFlexGroup.displayName = 'SearchRowFlexGroup'; +export const USERS_DROPDOWN = i18n.translate('xpack.securitySolution.notes.usersDropdownLabel', { + defaultMessage: 'Users', +}); export const SearchRow = React.memo(() => { const dispatch = useDispatch(); @@ -31,7 +25,7 @@ export const SearchRow = React.memo(() => { () => ({ placeholder: 'Search note contents', incremental: false, - 'data-test-subj': 'notes-search-bar', + 'data-test-subj': SEARCH_BAR_TEST_ID, }), [] ); @@ -43,14 +37,43 @@ export const SearchRow = React.memo(() => { [dispatch] ); + const { isLoading: isLoadingSuggestedUsers, data: userProfiles } = useSuggestUsers({ + searchTerm: '', + }); + const users = useMemo( + () => + (userProfiles || []).map((userProfile: UserProfileWithAvatar) => ({ + label: userProfile.user.full_name || userProfile.user.username, + })), + [userProfiles] + ); + + const [selectedUser, setSelectedUser] = useState<Array<EuiComboBoxOptionOption<string>>>(); + const onChange = useCallback( + (user: Array<EuiComboBoxOptionOption<string>>) => { + setSelectedUser(user); + dispatch(userFilterUsers(user.length > 0 ? user[0].label : '')); + }, + [dispatch] + ); + return ( - <SearchRowContainer> - <SearchRowFlexGroup gutterSize="s"> - <EuiFlexItem> - <EuiSearchBar box={searchBox} onChange={onQueryChange} defaultQuery="" /> - </EuiFlexItem> - </SearchRowFlexGroup> - </SearchRowContainer> + <EuiFlexGroup gutterSize="m"> + <EuiFlexItem> + <EuiSearchBar box={searchBox} onChange={onQueryChange} defaultQuery="" /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiComboBox + prepend={USERS_DROPDOWN} + singleSelection={{ asPlainText: true }} + options={users} + selectedOptions={selectedUser} + onChange={onChange} + isLoading={isLoadingSuggestedUsers} + data-test-subj={USER_SELECT_TEST_ID} + /> + </EuiFlexItem> + </EuiFlexGroup> ); }); diff --git a/x-pack/plugins/security_solution/public/notes/components/test_ids.ts b/x-pack/plugins/security_solution/public/notes/components/test_ids.ts index ac4eeb1948748..1464ed17d8764 100644 --- a/x-pack/plugins/security_solution/public/notes/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/notes/components/test_ids.ts @@ -19,3 +19,5 @@ export const OPEN_FLYOUT_BUTTON_TEST_ID = `${PREFIX}OpenFlyoutButton` as const; export const TIMELINE_DESCRIPTION_COMMENT_TEST_ID = `${PREFIX}TimelineDescriptionComment` as const; export const NOTE_CONTENT_BUTTON_TEST_ID = `${PREFIX}NoteContentButton` as const; export const NOTE_CONTENT_POPOVER_TEST_ID = `${PREFIX}NoteContentPopover` as const; +export const SEARCH_BAR_TEST_ID = `${PREFIX}SearchBar` as const; +export const USER_SELECT_TEST_ID = `${PREFIX}UserSelect` as const; diff --git a/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx b/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx index f0a337cb6c217..e34824d1ad814 100644 --- a/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx +++ b/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx @@ -23,6 +23,7 @@ import { selectNotesTableSelectedIds, selectNotesTableSearch, userSelectedBulkDelete, + selectNotesTableUserFilters, } from '..'; export const BATCH_ACTIONS = i18n.translate( @@ -51,6 +52,7 @@ export const NotesUtilityBar = React.memo(() => { const pagination = useSelector(selectNotesPagination); const sort = useSelector(selectNotesTableSort); const selectedItems = useSelector(selectNotesTableSelectedIds); + const notesUserFilters = useSelector(selectNotesTableUserFilters); const resultsCount = useMemo(() => { const { perPage, page, total } = pagination; const startOfCurrentPage = perPage * (page - 1) + 1; @@ -83,10 +85,19 @@ export const NotesUtilityBar = React.memo(() => { sortField: sort.field, sortOrder: sort.direction, filter: '', + userFilter: notesUserFilters, search: notesSearch, }) ); - }, [dispatch, pagination.page, pagination.perPage, sort.field, sort.direction, notesSearch]); + }, [ + dispatch, + pagination.page, + pagination.perPage, + sort.field, + sort.direction, + notesUserFilters, + notesSearch, + ]); return ( <UtilityBar border> <UtilityBarSection> diff --git a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx index 2b7f0f690532c..e329f0d75b911 100644 --- a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx +++ b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx @@ -36,6 +36,7 @@ import { selectNotesTablePendingDeleteIds, selectFetchNotesError, ReqStatus, + selectNotesTableUserFilters, } from '..'; import type { NotesState } from '..'; import { SearchRow } from '../components/search_row'; @@ -119,6 +120,7 @@ export const NoteManagementPage = () => { const pagination = useSelector(selectNotesPagination); const sort = useSelector(selectNotesTableSort); const notesSearch = useSelector(selectNotesTableSearch); + const notesUserFilters = useSelector(selectNotesTableUserFilters); const pendingDeleteIds = useSelector(selectNotesTablePendingDeleteIds); const isDeleteModalVisible = pendingDeleteIds.length > 0; const fetchNotesStatus = useSelector(selectFetchNotesStatus); @@ -134,10 +136,19 @@ export const NoteManagementPage = () => { sortField: sort.field, sortOrder: sort.direction, filter: '', + userFilter: notesUserFilters, search: notesSearch, }) ); - }, [dispatch, pagination.page, pagination.perPage, sort.field, sort.direction, notesSearch]); + }, [ + dispatch, + pagination.page, + pagination.perPage, + sort.field, + sort.direction, + notesUserFilters, + notesSearch, + ]); useEffect(() => { fetchData(); @@ -212,6 +223,7 @@ export const NoteManagementPage = () => { <Title title={i18n.NOTES} /> <EuiSpacer size="m" /> <SearchRow /> + <EuiSpacer size="m" /> <NotesUtilityBar /> <EuiBasicTable items={notes} diff --git a/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts b/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts index 3ab0333dc1abb..7cbaecf7d7135 100644 --- a/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts +++ b/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts @@ -46,6 +46,8 @@ import { fetchNotesBySavedObjectIds, selectNotesBySavedObjectId, selectSortedNotesBySavedObjectId, + userFilterUsers, + selectNotesTableUserFilters, userClosedCreateErrorToast, } from './notes.slice'; import type { NotesState } from './notes.slice'; @@ -69,7 +71,7 @@ const generateNoteMock = (documentId: string): Note => ({ const mockNote1 = generateNoteMock('1'); const mockNote2 = generateNoteMock('2'); -const initialNonEmptyState = { +const initialNonEmptyState: NotesState = { entities: { [mockNote1.noteId]: mockNote1, [mockNote2.noteId]: mockNote2, @@ -99,6 +101,7 @@ const initialNonEmptyState = { direction: 'desc' as const, }, filter: '', + userFilter: '', search: '', selectedIds: [], pendingDeleteIds: [], @@ -501,6 +504,17 @@ describe('notesSlice', () => { }); }); + describe('userFilterUsers', () => { + it('should set correct value to filter users', () => { + const action = { type: userFilterUsers.type, payload: 'abc' }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + userFilter: 'abc', + }); + }); + }); + describe('userSearchedNotes', () => { it('should set correct value to search notes', () => { const action = { type: userSearchedNotes.type, payload: 'abc' }; @@ -837,6 +851,14 @@ describe('notesSlice', () => { expect(selectNotesTableSearch(state)).toBe('test search'); }); + it('should select associated filter', () => { + const state = { + ...mockGlobalState, + notes: { ...initialNotesState, userFilter: 'abc' }, + }; + expect(selectNotesTableUserFilters(state)).toBe('abc'); + }); + it('should select notes table pending delete ids', () => { const state = { ...mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts b/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts index 979e984b5719b..d5a4e7d4ab14e 100644 --- a/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts +++ b/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts @@ -57,6 +57,7 @@ export interface NotesState extends EntityState<Note> { direction: 'asc' | 'desc'; }; filter: string; + userFilter: string; search: string; selectedIds: string[]; pendingDeleteIds: string[]; @@ -91,6 +92,7 @@ export const initialNotesState: NotesState = notesAdapter.getInitialState({ direction: 'desc', }, filter: '', + userFilter: '', search: '', selectedIds: [], pendingDeleteIds: [], @@ -124,12 +126,21 @@ export const fetchNotes = createAsyncThunk< sortField: string; sortOrder: string; filter: string; + userFilter: string; search: string; }, {} >('notes/fetchNotes', async (args) => { - const { page, perPage, sortField, sortOrder, filter, search } = args; - const res = await fetchNotesApi({ page, perPage, sortField, sortOrder, filter, search }); + const { page, perPage, sortField, sortOrder, filter, userFilter, search } = args; + const res = await fetchNotesApi({ + page, + perPage, + sortField, + sortOrder, + filter, + userFilter, + search, + }); return { ...normalizeEntities('notes' in res ? res.notes : []), totalCount: 'totalCount' in res ? res.totalCount : 0, @@ -152,7 +163,7 @@ export const deleteNotes = createAsyncThunk<string[], { ids: string[]; refetch?: await deleteNotesApi(ids); if (refetch) { const state = getState() as State; - const { search, pagination, sort } = state.notes; + const { search, pagination, userFilter, sort } = state.notes; dispatch( fetchNotes({ page: pagination.page, @@ -160,6 +171,7 @@ export const deleteNotes = createAsyncThunk<string[], { ids: string[]; refetch?: sortField: sort.field, sortOrder: sort.direction, filter: '', + userFilter, search, }) ); @@ -172,99 +184,102 @@ const notesSlice = createSlice({ name: 'notes', initialState: initialNotesState, reducers: { - userSelectedPage: (state, action: { payload: number }) => { + userSelectedPage: (state: NotesState, action: { payload: number }) => { state.pagination.page = action.payload; }, - userSelectedPerPage: (state, action: { payload: number }) => { + userSelectedPerPage: (state: NotesState, action: { payload: number }) => { state.pagination.perPage = action.payload; }, userSortedNotes: ( - state, + state: NotesState, action: { payload: { field: keyof Note; direction: 'asc' | 'desc' } } ) => { state.sort = action.payload; }, - userFilteredNotes: (state, action: { payload: string }) => { + userFilteredNotes: (state: NotesState, action: { payload: string }) => { state.filter = action.payload; }, - userSearchedNotes: (state, action: { payload: string }) => { + userFilterUsers: (state: NotesState, action: { payload: string }) => { + state.userFilter = action.payload; + }, + userSearchedNotes: (state: NotesState, action: { payload: string }) => { state.search = action.payload; }, - userSelectedRow: (state, action: { payload: string[] }) => { + userSelectedRow: (state: NotesState, action: { payload: string[] }) => { state.selectedIds = action.payload; }, - userClosedDeleteModal: (state) => { + userClosedDeleteModal: (state: NotesState) => { state.pendingDeleteIds = []; }, - userSelectedNotesForDeletion: (state, action: { payload: string }) => { + userSelectedNotesForDeletion: (state: NotesState, action: { payload: string }) => { state.pendingDeleteIds = [action.payload]; }, - userSelectedBulkDelete: (state) => { + userSelectedBulkDelete: (state: NotesState) => { state.pendingDeleteIds = state.selectedIds; }, - userClosedCreateErrorToast: (state) => { + userClosedCreateErrorToast: (state: NotesState) => { state.error.createNote = null; }, }, extraReducers(builder) { builder - .addCase(fetchNotesByDocumentIds.pending, (state) => { + .addCase(fetchNotesByDocumentIds.pending, (state: NotesState) => { state.status.fetchNotesByDocumentIds = ReqStatus.Loading; }) - .addCase(fetchNotesByDocumentIds.fulfilled, (state, action) => { + .addCase(fetchNotesByDocumentIds.fulfilled, (state: NotesState, action) => { notesAdapter.upsertMany(state, action.payload.entities.notes); state.status.fetchNotesByDocumentIds = ReqStatus.Succeeded; }) - .addCase(fetchNotesByDocumentIds.rejected, (state, action) => { + .addCase(fetchNotesByDocumentIds.rejected, (state: NotesState, action) => { state.status.fetchNotesByDocumentIds = ReqStatus.Failed; state.error.fetchNotesByDocumentIds = action.payload ?? action.error; }) - .addCase(fetchNotesBySavedObjectIds.pending, (state) => { + .addCase(fetchNotesBySavedObjectIds.pending, (state: NotesState) => { state.status.fetchNotesBySavedObjectIds = ReqStatus.Loading; }) - .addCase(fetchNotesBySavedObjectIds.fulfilled, (state, action) => { + .addCase(fetchNotesBySavedObjectIds.fulfilled, (state: NotesState, action) => { notesAdapter.upsertMany(state, action.payload.entities.notes); state.status.fetchNotesBySavedObjectIds = ReqStatus.Succeeded; }) - .addCase(fetchNotesBySavedObjectIds.rejected, (state, action) => { + .addCase(fetchNotesBySavedObjectIds.rejected, (state: NotesState, action) => { state.status.fetchNotesBySavedObjectIds = ReqStatus.Failed; state.error.fetchNotesBySavedObjectIds = action.payload ?? action.error; }) - .addCase(createNote.pending, (state) => { + .addCase(createNote.pending, (state: NotesState) => { state.status.createNote = ReqStatus.Loading; }) - .addCase(createNote.fulfilled, (state, action) => { + .addCase(createNote.fulfilled, (state: NotesState, action) => { notesAdapter.addMany(state, action.payload.entities.notes); state.status.createNote = ReqStatus.Succeeded; }) - .addCase(createNote.rejected, (state, action) => { + .addCase(createNote.rejected, (state: NotesState, action) => { state.status.createNote = ReqStatus.Failed; state.error.createNote = action.payload ?? action.error; }) - .addCase(deleteNotes.pending, (state) => { + .addCase(deleteNotes.pending, (state: NotesState) => { state.status.deleteNotes = ReqStatus.Loading; }) - .addCase(deleteNotes.fulfilled, (state, action) => { + .addCase(deleteNotes.fulfilled, (state: NotesState, action) => { notesAdapter.removeMany(state, action.payload); state.status.deleteNotes = ReqStatus.Succeeded; state.pendingDeleteIds = state.pendingDeleteIds.filter( (value) => !action.payload.includes(value) ); }) - .addCase(deleteNotes.rejected, (state, action) => { + .addCase(deleteNotes.rejected, (state: NotesState, action) => { state.status.deleteNotes = ReqStatus.Failed; state.error.deleteNotes = action.payload ?? action.error; }) - .addCase(fetchNotes.pending, (state) => { + .addCase(fetchNotes.pending, (state: NotesState) => { state.status.fetchNotes = ReqStatus.Loading; }) - .addCase(fetchNotes.fulfilled, (state, action) => { + .addCase(fetchNotes.fulfilled, (state: NotesState, action) => { notesAdapter.setAll(state, action.payload.entities.notes); state.pagination.total = action.payload.totalCount; state.status.fetchNotes = ReqStatus.Succeeded; state.selectedIds = []; }) - .addCase(fetchNotes.rejected, (state, action) => { + .addCase(fetchNotes.rejected, (state: NotesState, action) => { state.status.fetchNotes = ReqStatus.Failed; state.error.fetchNotes = action.payload ?? action.error; }); @@ -307,6 +322,8 @@ export const selectNotesTableSelectedIds = (state: State) => state.notes.selecte export const selectNotesTableSearch = (state: State) => state.notes.search; +export const selectNotesTableUserFilters = (state: State) => state.notes.userFilter; + export const selectNotesTablePendingDeleteIds = (state: State) => state.notes.pendingDeleteIds; export const selectFetchNotesError = (state: State) => state.notes.error.fetchNotes; @@ -394,6 +411,7 @@ export const { userSelectedPerPage, userSortedNotes, userFilteredNotes, + userFilterUsers, userSearchedNotes, userSelectedRow, userClosedDeleteModal, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index 925379baedad5..bc6c83e2b159c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -11,6 +11,7 @@ import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey' import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; import { nodeBuilder } from '@kbn/es-query'; +import type { KueryNode } from '@kbn/es-query'; import { timelineSavedObjectType } from '../../saved_object_mappings'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { MAX_UNASSOCIATED_NOTES, NOTE_URL } from '../../../../../common/constants'; @@ -126,6 +127,22 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter) => { sortOrder, filter, }; + + // retrieve all the notes created by a specific user + const userFilter = queryParams?.userFilter; + if (userFilter) { + // we need to combine the associatedFilter with the filter query + // we have to type case here because the filter is a string (from the schema) and that cannot be changed as it would be a breaking change + const filterAsKueryNode: KueryNode = (filter || '') as unknown as KueryNode; + + options.filter = nodeBuilder.and([ + nodeBuilder.is(`${noteSavedObjectType}.attributes.createdBy`, userFilter), + filterAsKueryNode, + ]); + } else { + options.filter = filter; + } + const res = await getAllSavedNote(frameworkRequest, options); const body: GetNotesResponse = res ?? {}; return response.ok({ body }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts index a5944dc8c6149..5bf4d61c8b595 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts @@ -7,7 +7,10 @@ import type SuperTest from 'supertest'; import { v4 as uuidv4 } from 'uuid'; -import { BareNote, TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline'; +import { + PersistNoteRouteRequestBody, + TimelineTypeEnum, +} from '@kbn/security-solution-plugin/common/api/timeline'; import { NOTE_URL } from '@kbn/security-solution-plugin/common/constants'; import type { Client } from '@elastic/elasticsearch'; import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; @@ -58,7 +61,6 @@ export const createNote = async ( note: { documentId?: string; savedObjectId?: string; - user?: string; text: string; } ) => @@ -70,9 +72,9 @@ export const createNote = async ( eventId: note.documentId || '', timelineId: note.savedObjectId || '', created: Date.now(), - createdBy: note.user || 'elastic', + createdBy: 'elastic', updated: Date.now(), - updatedBy: note.user || 'elastic', + updatedBy: 'elastic', note: note.text, - } as BareNote, - }); + }, + } as PersistNoteRouteRequestBody); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts index dabb453f80158..8a636358c2649 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts @@ -408,8 +408,41 @@ export default function ({ getService }: FtrProviderContext) { }); // TODO should add more tests for the filter query parameter (I don't know how it's supposed to work) - // TODO should add more tests for the MAX_UNASSOCIATED_NOTES advanced settings values + + // TODO figure out why this test is failing on CI but not locally + // we can't really test for other users because the persistNote endpoint forces overrideOwner to be true then all the notes created here are owned by the elastic user + it.skip('should retrieve all notes that have been created by a specific user', async () => { + await Promise.all([ + createNote(supertest, { text: 'first note' }), + createNote(supertest, { text: 'second note' }), + ]); + + const response = await supertest + .get('/api/note?userFilter=elastic') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount } = response.body; + + expect(totalCount).to.be(2); + }); + + it('should return nothing if no notes have been created by that user', async () => { + await Promise.all([ + createNote(supertest, { text: 'first note' }), + createNote(supertest, { text: 'second note' }), + ]); + + const response = await supertest + .get('/api/note?userFilter=user1') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const { totalCount } = response.body; + + expect(totalCount).to.be(0); + }); }); }); } From 983a3e5723f7c2ab6e33663e03355f431723b1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= <contact@patrykkopycinski.com> Date: Wed, 16 Oct 2024 05:41:57 +0200 Subject: [PATCH 82/84] Kb settings followup (#195733) --- .../data_views/data_views_api_client.test.ts | 12 + .../data_views/data_views_api_client.ts | 2 +- .../assistant/assistant_overlay/index.tsx | 12 +- .../flyout/index.tsx | 4 + .../inline_actions/index.tsx | 1 + .../impl/assistant/index.test.tsx | 106 ----- .../impl/assistant/index.tsx | 8 +- .../alerts_settings/alerts_settings.test.tsx | 4 +- .../alerts_settings/alerts_settings_modal.tsx | 63 +++ .../settings/assistant_settings.test.tsx | 23 +- .../assistant/settings/assistant_settings.tsx | 118 +---- .../assistant_settings_button.test.tsx | 7 - .../settings/assistant_settings_button.tsx | 46 +- .../assistant_settings_management.test.tsx | 52 +- .../assistant_settings_management.tsx | 29 +- .../impl/assistant/settings/const.ts | 14 +- .../settings_context_menu.tsx | 54 ++- .../impl/assistant_context/constants.tsx | 2 +- .../impl/assistant_context/index.tsx | 23 +- .../impl/assistant_context/types.tsx | 2 + .../connector_missing_callout/index.test.tsx | 2 + .../anonymization_settings/index.test.tsx | 1 + .../index.tsx | 75 ++- .../impl/knowledge_base/alerts_range.tsx | 1 + .../knowledge_base_settings.tsx | 15 +- .../add_entry_button.tsx | 8 +- .../document_entry_editor.tsx | 214 +++++---- .../helpers.ts | 4 + .../index.test.tsx | 244 ++++++++++ .../index.tsx | 103 ++-- .../index_entry_editor.test.tsx | 150 ++++++ .../index_entry_editor.tsx | 446 ++++++++++-------- .../translations.ts | 28 +- .../use_knowledge_base_table.tsx | 97 ++-- .../mock/test_providers/test_providers.tsx | 3 + .../mock/test_providers/test_providers.tsx | 3 + .../src/assistant/kibana_sub_features.ts | 41 ++ .../src/assistant/product_feature_config.ts | 5 +- .../features/src/product_features_keys.ts | 1 + .../create_knowledge_base_entry.ts | 9 + .../knowledge_base/index.ts | 15 +- .../server/ai_assistant_service/index.ts | 1 + .../knowledge_base/entries/create_route.ts | 1 - .../server/routes/request_context_factory.ts | 14 +- x-pack/plugins/security_solution/kibana.jsonc | 5 +- .../public/assistant/overlay.tsx | 20 +- .../public/assistant/provider.tsx | 2 + .../management_settings.test.tsx | 15 +- .../stack_management/management_settings.tsx | 90 +++- .../use_assistant_availability/index.tsx | 5 + .../common/mock/mock_assistant_provider.tsx | 3 + .../rule_status_failed_callout.test.tsx | 3 + .../right/hooks/use_assistant.test.tsx | 3 + .../plugins/security_solution/public/types.ts | 2 + .../plugins/security_solution/tsconfig.json | 2 + .../public/navigation/management_cards.ts | 35 +- .../apis/security/privileges.ts | 1 + .../apis/security/privileges_basic.ts | 1 + .../cypress/tasks/assistant.ts | 2 - 59 files changed, 1458 insertions(+), 794 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.test.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx diff --git a/src/plugins/data_views/public/data_views/data_views_api_client.test.ts b/src/plugins/data_views/public/data_views/data_views_api_client.test.ts index 1ca1023423bea..8e1261802fbbc 100644 --- a/src/plugins/data_views/public/data_views/data_views_api_client.test.ts +++ b/src/plugins/data_views/public/data_views/data_views_api_client.test.ts @@ -17,6 +17,7 @@ describe('IndexPatternsApiClient', () => { let indexPatternsApiClient: DataViewsApiClient; beforeEach(() => { + jest.clearAllMocks(); fetchSpy = jest.spyOn(http, 'fetch').mockImplementation(() => Promise.resolve({})); indexPatternsApiClient = new DataViewsApiClient(http as HttpSetup, () => Promise.resolve(undefined) @@ -46,4 +47,15 @@ describe('IndexPatternsApiClient', () => { version: '1', // version header }); }); + + test('Correctly formats fieldTypes argument', async function () { + const fieldTypes = ['text', 'keyword']; + await indexPatternsApiClient.getFieldsForWildcard({ + pattern: 'blah', + fieldTypes, + allowHidden: false, + }); + + expect(fetchSpy.mock.calls[0][1].query.field_types).toEqual(fieldTypes); + }); }); diff --git a/src/plugins/data_views/public/data_views/data_views_api_client.ts b/src/plugins/data_views/public/data_views/data_views_api_client.ts index 3b91ebcbf5d78..e569e7f25bff6 100644 --- a/src/plugins/data_views/public/data_views/data_views_api_client.ts +++ b/src/plugins/data_views/public/data_views/data_views_api_client.ts @@ -112,7 +112,7 @@ export class DataViewsApiClient implements IDataViewsApiClient { allow_no_index: allowNoIndex, include_unmapped: includeUnmapped, fields, - fieldTypes, + field_types: fieldTypes, // default to undefined to keep value out of URL params and improve caching allow_hidden: allowHidden || undefined, include_empty_fields: includeEmptyFields, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx index 1e43dcb889e9b..b9457e5cfea68 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx @@ -12,11 +12,7 @@ import useEvent from 'react-use/lib/useEvent'; import { css } from '@emotion/react'; // eslint-disable-next-line @kbn/eslint/module_migration import { createGlobalStyle } from 'styled-components'; -import { - ShowAssistantOverlayProps, - useAssistantContext, - UserAvatar, -} from '../../assistant_context'; +import { ShowAssistantOverlayProps, useAssistantContext } from '../../assistant_context'; import { Assistant, CONVERSATION_SIDE_PANEL_WIDTH } from '..'; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; @@ -25,9 +21,6 @@ const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; * Modal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever * component currently has focus and any specific context it may provide through the SAssInterface. */ -export interface Props { - currentUserAvatar?: UserAvatar; -} export const UnifiedTimelineGlobalStyles = createGlobalStyle` body:has(.timeline-portal-overlay-mask) .euiOverlayMask { @@ -35,7 +28,7 @@ export const UnifiedTimelineGlobalStyles = createGlobalStyle` } `; -export const AssistantOverlay = React.memo<Props>(({ currentUserAvatar }) => { +export const AssistantOverlay = React.memo(() => { const [isModalVisible, setIsModalVisible] = useState(false); // Why is this named Title and not Id? const [conversationTitle, setConversationTitle] = useState<string | undefined>(undefined); @@ -144,7 +137,6 @@ export const AssistantOverlay = React.memo<Props>(({ currentUserAvatar }) => { onCloseFlyout={handleCloseModal} chatHistoryVisible={chatHistoryVisible} setChatHistoryVisible={toggleChatHistory} - currentUserAvatar={currentUserAvatar} /> </EuiFlyoutResizable> <UnifiedTimelineGlobalStyles /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx index ac0109f31b9b7..b54f43c6a3aa4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx @@ -28,6 +28,7 @@ interface Props { onSaveCancelled: () => void; onSaveConfirmed: () => void; saveButtonDisabled?: boolean; + saveButtonLoading?: boolean; } const FlyoutComponent: React.FC<Props> = ({ @@ -38,9 +39,11 @@ const FlyoutComponent: React.FC<Props> = ({ onSaveCancelled, onSaveConfirmed, saveButtonDisabled = false, + saveButtonLoading = false, }) => { return flyoutVisible ? ( <EuiFlyout + data-test-subj={'flyout'} ownFocus onClose={onClose} css={css` @@ -74,6 +77,7 @@ const FlyoutComponent: React.FC<Props> = ({ onClick={onSaveConfirmed} iconType="check" disabled={saveButtonDisabled} + isLoading={saveButtonLoading} fill > {i18n.FLYOUT_SAVE_BUTTON_TITLE} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx index f89ad5912a60a..06e0c8ebcc977 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx @@ -48,6 +48,7 @@ export const useInlineActions = <T extends { isDefault?: boolean | undefined }>( }, { name: i18n.DELETE_BUTTON, + 'data-test-subj': 'delete-button', description: i18n.DELETE_BUTTON, icon: 'trash', type: 'icon', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx index d042a4cfd96f5..08bac25c0a522 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -24,7 +24,6 @@ import { Conversation } from '../assistant_context/types'; import * as all from './chat_send/use_chat_send'; import { useConversation } from './use_conversation'; import { AIConnector } from '../connectorland/connector_selector'; -import { omit } from 'lodash'; jest.mock('../connectorland/use_load_connectors'); jest.mock('../connectorland/connector_setup'); @@ -141,111 +140,6 @@ describe('Assistant', () => { >); }); - describe('persistent storage', () => { - it('should refetchCurrentUserConversations after settings save button click', async () => { - const chatSendSpy = jest.spyOn(all, 'useChatSend'); - await renderAssistant(); - - fireEvent.click(screen.getByTestId('settings')); - - jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ - data: { - ...mockData, - welcome_id: { - ...mockData.welcome_id, - apiConfig: { newProp: true }, - }, - }, - isLoading: false, - refetch: jest.fn().mockResolvedValue({ - isLoading: false, - data: { - ...mockData, - welcome_id: { - ...mockData.welcome_id, - apiConfig: { newProp: true }, - }, - }, - }), - isFetched: true, - } as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>); - - await act(async () => { - fireEvent.click(screen.getByTestId('save-button')); - }); - - expect(chatSendSpy).toHaveBeenLastCalledWith( - expect.objectContaining({ - currentConversation: { - apiConfig: { newProp: true }, - category: 'assistant', - id: mockData.welcome_id.id, - messages: [], - title: 'Welcome', - replacements: {}, - }, - }) - ); - }); - - it('should refetchCurrentUserConversations after settings save button click, but do not update convos when refetch returns bad results', async () => { - jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ - data: mockData, - isLoading: false, - refetch: jest.fn().mockResolvedValue({ - isLoading: false, - data: omit(mockData, 'welcome_id'), - }), - isFetched: true, - } as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>); - const chatSendSpy = jest.spyOn(all, 'useChatSend'); - await renderAssistant(); - - fireEvent.click(screen.getByTestId('settings')); - await act(async () => { - fireEvent.click(screen.getByTestId('save-button')); - }); - - expect(chatSendSpy).toHaveBeenLastCalledWith( - expect.objectContaining({ - currentConversation: { - apiConfig: { connectorId: '123' }, - replacements: {}, - category: 'assistant', - id: mockData.welcome_id.id, - messages: [], - title: 'Welcome', - }, - }) - ); - }); - - it('should delete conversation when delete button is clicked', async () => { - await renderAssistant(); - const deleteButton = screen.getAllByTestId('delete-option')[0]; - await act(async () => { - fireEvent.click(deleteButton); - }); - - await act(async () => { - fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); - }); - - await waitFor(() => { - expect(mockDeleteConvo).toHaveBeenCalledWith(mockData.electric_sheep_id.id); - }); - }); - it('should refetchCurrentUserConversations after clear chat history button click', async () => { - await renderAssistant(); - fireEvent.click(screen.getByTestId('chat-context-menu')); - fireEvent.click(screen.getByTestId('clear-chat')); - fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); - await waitFor(() => { - expect(clearConversation).toHaveBeenCalled(); - expect(refetchResults).toHaveBeenCalled(); - }); - }); - }); describe('when selected conversation changes and some connectors are loaded', () => { it('should persist the conversation id to local storage', async () => { const getConversation = jest.fn().mockResolvedValue(mockData.electric_sheep_id); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index c52d94138b839..b20122f822164 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -38,7 +38,7 @@ import { ChatSend } from './chat_send'; import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations'; import { getDefaultConnector } from './helpers'; -import { useAssistantContext, UserAvatar } from '../assistant_context'; +import { useAssistantContext } from '../assistant_context'; import { ContextPills } from './context_pills'; import { getNewSelectedPromptContext } from '../data_anonymization/get_new_selected_prompt_context'; import type { PromptContext, SelectedPromptContext } from './prompt_context/types'; @@ -61,7 +61,6 @@ const CommentContainer = styled('span')` export interface Props { chatHistoryVisible?: boolean; conversationTitle?: string; - currentUserAvatar?: UserAvatar; onCloseFlyout?: () => void; promptContextId?: string; setChatHistoryVisible?: Dispatch<SetStateAction<boolean>>; @@ -75,7 +74,6 @@ export interface Props { const AssistantComponent: React.FC<Props> = ({ chatHistoryVisible, conversationTitle, - currentUserAvatar, onCloseFlyout, promptContextId = '', setChatHistoryVisible, @@ -90,12 +88,10 @@ const AssistantComponent: React.FC<Props> = ({ getLastConversationId, http, promptContexts, - setCurrentUserAvatar, + currentUserAvatar, setLastConversationId, } = useAssistantContext(); - setCurrentUserAvatar(currentUserAvatar); - const [selectedPromptContexts, setSelectedPromptContexts] = useState< Record<string, SelectedPromptContext> >({}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.test.tsx index 2a5cae76d5e77..b916fb348dd50 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.test.tsx @@ -31,10 +31,10 @@ describe('AlertsSettings', () => { ); const rangeSlider = screen.getByTestId('alertsRange'); - fireEvent.change(rangeSlider, { target: { value: '10' } }); + fireEvent.change(rangeSlider, { target: { value: '90' } }); expect(setUpdatedKnowledgeBaseSettings).toHaveBeenCalledWith({ - latestAlerts: 10, + latestAlerts: 90, }); }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx new file mode 100644 index 0000000000000..4e362a4bec8be --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import { ALERTS_LABEL } from '../../../knowledge_base/translations'; +import { + DEFAULT_CONVERSATIONS, + DEFAULT_PROMPTS, + useSettingsUpdater, +} from '../use_settings_updater/use_settings_updater'; +import { AlertsSettings } from './alerts_settings'; +import { CANCEL, SAVE } from '../translations'; + +interface AlertSettingsModalProps { + onClose: () => void; +} + +export const AlertsSettingsModal = ({ onClose }: AlertSettingsModalProps) => { + const { knowledgeBase, setUpdatedKnowledgeBaseSettings, saveSettings } = useSettingsUpdater( + DEFAULT_CONVERSATIONS, // Alerts settings do not require conversations + DEFAULT_PROMPTS, // Alerts settings do not require prompts + false, // Alerts settings do not require conversations + false // Alerts settings do not require prompts + ); + + const handleSave = useCallback(() => { + saveSettings(); + onClose(); + }, [onClose, saveSettings]); + + return ( + <EuiModal onClose={onClose}> + <EuiModalHeader> + <EuiModalHeaderTitle>{ALERTS_LABEL}</EuiModalHeaderTitle> + </EuiModalHeader> + <EuiModalBody> + <AlertsSettings + knowledgeBase={knowledgeBase} + setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} + /> + </EuiModalBody> + <EuiModalFooter> + <EuiButtonEmpty onClick={onClose}>{CANCEL}</EuiButtonEmpty> + <EuiButton type="submit" onClick={handleSave} fill> + {SAVE} + </EuiButton> + </EuiModalFooter> + </EuiModal> + ); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx index 9fb8db972e482..14bfcb4cdbbec 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx @@ -64,12 +64,12 @@ jest.mock('../../assistant_context'); jest.mock('.', () => { return { - AnonymizationSettings: () => <span data-test-subj="ANONYMIZATION_TAB-tab" />, - ConversationSettings: () => <span data-test-subj="CONVERSATIONS_TAB-tab" />, - EvaluationSettings: () => <span data-test-subj="EVALUATION_TAB-tab" />, - KnowledgeBaseSettings: () => <span data-test-subj="KNOWLEDGE_BASE_TAB-tab" />, - QuickPromptSettings: () => <span data-test-subj="QUICK_PROMPTS_TAB-tab" />, - SystemPromptSettings: () => <span data-test-subj="SYSTEM_PROMPTS_TAB-tab" />, + AnonymizationSettings: () => <span data-test-subj="anonymization-tab" />, + ConversationSettings: () => <span data-test-subj="conversations-tab" />, + EvaluationSettings: () => <span data-test-subj="evaluation-tab" />, + KnowledgeBaseSettings: () => <span data-test-subj="knowledge_base-tab" />, + QuickPromptSettings: () => <span data-test-subj="quick_prompts-tab" />, + SystemPromptSettings: () => <span data-test-subj="system_prompts-tab" />, }; }); @@ -136,17 +136,6 @@ describe('AssistantSettings', () => { QUICK_PROMPTS_TAB, SYSTEM_PROMPTS_TAB, ])('%s', (tab) => { - it('Opens the tab on button click', () => { - (useAssistantContext as jest.Mock).mockImplementation(() => ({ - ...mockContext, - selectedSettingsTab: tab === CONVERSATIONS_TAB ? ANONYMIZATION_TAB : CONVERSATIONS_TAB, - })); - const { getByTestId } = render(<AssistantSettings {...testProps} />, { - wrapper, - }); - fireEvent.click(getByTestId(`${tab}-button`)); - expect(setSelectedSettingsTab).toHaveBeenCalledWith(tab); - }); it('renders with the correct tab open', () => { (useAssistantContext as jest.Mock).mockImplementation(() => ({ ...mockContext, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx index f92ca3fc3c763..f325e411bae2b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx @@ -9,14 +9,10 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButton, EuiButtonEmpty, - EuiIcon, EuiModal, EuiModalFooter, - EuiKeyPadMenu, - EuiKeyPadMenuItem, EuiPage, EuiPageBody, - EuiPageSidebar, EuiSplitPanel, } from '@elastic/eui'; @@ -80,13 +76,7 @@ export const AssistantSettings: React.FC<Props> = React.memo( conversations, conversationsLoaded, }) => { - const { - assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, - http, - toasts, - selectedSettingsTab, - setSelectedSettingsTab, - } = useAssistantContext(); + const { http, toasts, selectedSettingsTab, setSelectedSettingsTab } = useAssistantContext(); useEffect(() => { if (selectedSettingsTab == null) { @@ -211,112 +201,6 @@ export const AssistantSettings: React.FC<Props> = React.memo( return ( <StyledEuiModal data-test-subj={TEST_IDS.SETTINGS_MODAL} onClose={onClose}> <EuiPage paddingSize="none"> - <EuiPageSidebar - paddingSize="xs" - css={css` - min-inline-size: unset !important; - max-width: 104px; - `} - > - <EuiKeyPadMenu> - <EuiKeyPadMenuItem - id={CONVERSATIONS_TAB} - label={i18n.CONVERSATIONS_MENU_ITEM} - isSelected={!selectedSettingsTab || selectedSettingsTab === CONVERSATIONS_TAB} - onClick={() => setSelectedSettingsTab(CONVERSATIONS_TAB)} - data-test-subj={`${CONVERSATIONS_TAB}-button`} - > - <> - <EuiIcon - type="editorComment" - size="xl" - css={css` - position: relative; - top: -10px; - `} - /> - <EuiIcon - type="editorComment" - size="l" - css={css` - position: relative; - transform: rotateY(180deg); - top: -7px; - `} - /> - </> - </EuiKeyPadMenuItem> - <EuiKeyPadMenuItem - id={QUICK_PROMPTS_TAB} - label={i18n.QUICK_PROMPTS_MENU_ITEM} - isSelected={selectedSettingsTab === QUICK_PROMPTS_TAB} - onClick={() => setSelectedSettingsTab(QUICK_PROMPTS_TAB)} - data-test-subj={`${QUICK_PROMPTS_TAB}-button`} - > - <> - <EuiIcon type="editorComment" size="xxl" /> - <EuiIcon - type="bolt" - size="s" - color="warning" - css={css` - position: absolute; - top: 11px; - left: 14px; - `} - /> - </> - </EuiKeyPadMenuItem> - <EuiKeyPadMenuItem - id={SYSTEM_PROMPTS_TAB} - label={i18n.SYSTEM_PROMPTS_MENU_ITEM} - isSelected={selectedSettingsTab === SYSTEM_PROMPTS_TAB} - onClick={() => setSelectedSettingsTab(SYSTEM_PROMPTS_TAB)} - data-test-subj={`${SYSTEM_PROMPTS_TAB}-button`} - > - <EuiIcon type="editorComment" size="xxl" /> - <EuiIcon - type="storage" - size="s" - color="success" - css={css` - position: absolute; - top: 11px; - left: 14px; - `} - /> - </EuiKeyPadMenuItem> - <EuiKeyPadMenuItem - id={ANONYMIZATION_TAB} - label={i18n.ANONYMIZATION_MENU_ITEM} - isSelected={selectedSettingsTab === ANONYMIZATION_TAB} - onClick={() => setSelectedSettingsTab(ANONYMIZATION_TAB)} - data-test-subj={`${ANONYMIZATION_TAB}-button`} - > - <EuiIcon type="eyeClosed" size="l" /> - </EuiKeyPadMenuItem> - <EuiKeyPadMenuItem - id={KNOWLEDGE_BASE_TAB} - label={i18n.KNOWLEDGE_BASE_MENU_ITEM} - isSelected={selectedSettingsTab === KNOWLEDGE_BASE_TAB} - onClick={() => setSelectedSettingsTab(KNOWLEDGE_BASE_TAB)} - data-test-subj={`${KNOWLEDGE_BASE_TAB}-button`} - > - <EuiIcon type="notebookApp" size="l" /> - </EuiKeyPadMenuItem> - {modelEvaluatorEnabled && ( - <EuiKeyPadMenuItem - id={EVALUATION_TAB} - label={i18n.EVALUATION_MENU_ITEM} - isSelected={selectedSettingsTab === EVALUATION_TAB} - onClick={() => setSelectedSettingsTab(EVALUATION_TAB)} - data-test-subj={`${EVALUATION_TAB}-button`} - > - <EuiIcon type="crossClusterReplicationApp" size="l" /> - </EuiKeyPadMenuItem> - )} - </EuiKeyPadMenu> - </EuiPageSidebar> <EuiPageBody paddingSize="none" panelled={true}> <EuiSplitPanel.Outer grow={true}> <EuiSplitPanel.Inner diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx index 84ce96b829558..0b00a38282ebe 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx @@ -11,7 +11,6 @@ import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/c import { AssistantSettingsButton } from './assistant_settings_button'; import { welcomeConvo } from '../../mock/conversation'; -import { CONVERSATIONS_TAB } from './const'; const setIsSettingsModalVisible = jest.fn(); const onConversationSelected = jest.fn(); @@ -57,12 +56,6 @@ describe('AssistantSettingsButton', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('Clicking the settings gear opens the conversations tab', () => { - const { getByTestId } = render(<AssistantSettingsButton {...testProps} />); - fireEvent.click(getByTestId('settings')); - expect(setSelectedSettingsTab).toHaveBeenCalledWith(CONVERSATIONS_TAB); - expect(setIsSettingsModalVisible).toHaveBeenCalledWith(true); - }); it('Settings modal is visible and calls correct actions per click', () => { const { getByTestId } = render( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx index 0767916d00ad7..40bf1e740ab60 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx @@ -6,8 +6,6 @@ */ import React, { useCallback } from 'react'; -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; - import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query'; import { DataStreamApis } from '../use_data_stream_apis'; import { AIConnector } from '../../connectorland/connector_selector'; @@ -15,7 +13,6 @@ import { Conversation } from '../../..'; import { AssistantSettings } from './assistant_settings'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; -import { CONVERSATIONS_TAB } from './const'; interface Props { defaultConnector?: AIConnector; @@ -48,7 +45,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo( refetchCurrentUserConversations, refetchPrompts, }) => { - const { toasts, setSelectedSettingsTab } = useAssistantContext(); + const { toasts } = useAssistantContext(); // Modal control functions const cleanupAndCloseModal = useCallback(() => { @@ -76,37 +73,18 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo( [cleanupAndCloseModal, refetchCurrentUserConversations, refetchPrompts, toasts] ); - const handleShowConversationSettings = useCallback(() => { - setSelectedSettingsTab(CONVERSATIONS_TAB); - setIsSettingsModalVisible(true); - }, [setIsSettingsModalVisible, setSelectedSettingsTab]); - return ( - <> - <EuiToolTip position="right" content={i18n.SETTINGS_TOOLTIP}> - <EuiButtonIcon - aria-label={i18n.SETTINGS} - data-test-subj="settings" - onClick={handleShowConversationSettings} - isDisabled={isDisabled} - iconType="gear" - size="xs" - color="text" - /> - </EuiToolTip> - - {isSettingsModalVisible && ( - <AssistantSettings - defaultConnector={defaultConnector} - selectedConversationId={selectedConversationId} - onConversationSelected={onConversationSelected} - onClose={handleCloseModal} - onSave={handleSave} - conversations={conversations} - conversationsLoaded={conversationsLoaded} - /> - )} - </> + isSettingsModalVisible && ( + <AssistantSettings + defaultConnector={defaultConnector} + selectedConversationId={selectedConversationId} + onConversationSelected={onConversationSelected} + onClose={handleCloseModal} + onSave={handleSave} + conversations={conversations} + conversationsLoaded={conversationsLoaded} + /> + ) ); } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx index dd472b3ee87ab..fe8c81ce1c404 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx @@ -16,8 +16,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AssistantSettingsManagement } from './assistant_settings_management'; import { - ANONYMIZATION_TAB, CONNECTORS_TAB, + ANONYMIZATION_TAB, CONVERSATIONS_TAB, EVALUATION_TAB, KNOWLEDGE_BASE_TAB, @@ -40,15 +40,12 @@ const mockValues = { quickPromptSettings: [], }; -const setSelectedSettingsTab = jest.fn(); const mockContext = { basePromptContexts: MOCK_QUICK_PROMPTS, - setSelectedSettingsTab, http: { get: jest.fn(), }, assistantFeatures: { assistantModelEvaluation: true }, - selectedSettingsTab: null, assistantAvailability: { isAssistantEnabled: true, }, @@ -58,39 +55,42 @@ const mockDataViews = { getIndices: jest.fn(), } as unknown as DataViewsContract; +const onTabChange = jest.fn(); const testProps = { selectedConversation: welcomeConvo, dataViews: mockDataViews, + onTabChange, + currentTab: CONNECTORS_TAB, }; jest.mock('../../assistant_context'); jest.mock('../../connectorland/connector_settings_management', () => ({ - ConnectorsSettingsManagement: () => <span data-test-subj="CONNECTORS_TAB-tab" />, + ConnectorsSettingsManagement: () => <span data-test-subj="connectors-tab" />, })); jest.mock('../conversations/conversation_settings_management', () => ({ - ConversationSettingsManagement: () => <span data-test-subj="CONVERSATIONS_TAB-tab" />, + ConversationSettingsManagement: () => <span data-test-subj="conversations-tab" />, })); jest.mock('../quick_prompts/quick_prompt_settings_management', () => ({ - QuickPromptSettingsManagement: () => <span data-test-subj="QUICK_PROMPTS_TAB-tab" />, + QuickPromptSettingsManagement: () => <span data-test-subj="quick_prompts-tab" />, })); jest.mock('../prompt_editor/system_prompt/system_prompt_settings_management', () => ({ - SystemPromptSettingsManagement: () => <span data-test-subj="SYSTEM_PROMPTS_TAB-tab" />, + SystemPromptSettingsManagement: () => <span data-test-subj="system_prompts-tab" />, })); jest.mock('../../knowledge_base/knowledge_base_settings_management', () => ({ - KnowledgeBaseSettingsManagement: () => <span data-test-subj="KNOWLEDGE_BASE_TAB-tab" />, + KnowledgeBaseSettingsManagement: () => <span data-test-subj="knowledge_base-tab" />, })); jest.mock('../../data_anonymization/settings/anonymization_settings_management', () => ({ - AnonymizationSettingsManagement: () => <span data-test-subj="ANONYMIZATION_TAB-tab" />, + AnonymizationSettingsManagement: () => <span data-test-subj="anonymization-tab" />, })); jest.mock('.', () => { return { - EvaluationSettings: () => <span data-test-subj="EVALUATION_TAB-tab" />, + EvaluationSettings: () => <span data-test-subj="evaluation-tab" />, }; }); @@ -138,25 +138,23 @@ describe('AssistantSettingsManagement', () => { SYSTEM_PROMPTS_TAB, ])('%s', (tab) => { it('Opens the tab on button click', () => { - (useAssistantContext as jest.Mock).mockImplementation(() => ({ - ...mockContext, - selectedSettingsTab: tab, - })); - const { getByTestId } = render(<AssistantSettingsManagement {...testProps} />, { - wrapper, - }); + const { getByTestId } = render( + <AssistantSettingsManagement {...testProps} currentTab={tab} />, + { + wrapper, + } + ); fireEvent.click(getByTestId(`settingsPageTab-${tab}`)); - expect(setSelectedSettingsTab).toHaveBeenCalledWith(tab); + expect(onTabChange).toHaveBeenCalledWith(tab); }); it('renders with the correct tab open', () => { - (useAssistantContext as jest.Mock).mockImplementation(() => ({ - ...mockContext, - selectedSettingsTab: tab, - })); - const { getByTestId } = render(<AssistantSettingsManagement {...testProps} />, { - wrapper, - }); - expect(getByTestId(`${tab}-tab`)).toBeInTheDocument(); + const { getByTestId } = render( + <AssistantSettingsManagement {...testProps} currentTab={tab} />, + { + wrapper, + } + ); + expect(getByTestId(`tab-${tab}`)).toBeInTheDocument(); }); }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx index 4c50d14a5662e..12b26da336e72 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import React, { useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { EuiAvatar, EuiPageTemplate, EuiTitle, useEuiShadow, useEuiTheme } from '@elastic/eui'; - import { css } from '@emotion/react'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import { Conversation } from '../../..'; @@ -32,10 +31,13 @@ import { } from './const'; import { KnowledgeBaseSettingsManagement } from '../../knowledge_base/knowledge_base_settings_management'; import { EvaluationSettings } from '.'; +import { SettingsTabs } from './types'; interface Props { dataViews: DataViewsContract; selectedConversation: Conversation; + onTabChange?: (tabId: string) => void; + currentTab?: SettingsTabs; } /** @@ -43,14 +45,16 @@ interface Props { * anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag. */ export const AssistantSettingsManagement: React.FC<Props> = React.memo( - ({ dataViews, selectedConversation: defaultSelectedConversation }) => { + ({ + dataViews, + selectedConversation: defaultSelectedConversation, + onTabChange, + currentTab: selectedSettingsTab, + }) => { const { assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, http, - selectedSettingsTab, - setSelectedSettingsTab, } = useAssistantContext(); - const { data: connectors } = useLoadConnectors({ http, }); @@ -59,12 +63,6 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo( const { euiTheme } = useEuiTheme(); const headerIconShadow = useEuiShadow('s'); - useEffect(() => { - if (selectedSettingsTab == null) { - setSelectedSettingsTab(CONNECTORS_TAB); - } - }, [selectedSettingsTab, setSelectedSettingsTab]); - const tabsConfig = useMemo( () => [ { @@ -107,10 +105,12 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo( return tabsConfig.map((t) => ({ ...t, 'data-test-subj': `settingsPageTab-${t.id}`, - onClick: () => setSelectedSettingsTab(t.id), + onClick: () => { + onTabChange?.(t.id); + }, isSelected: t.id === selectedSettingsTab, })); - }, [setSelectedSettingsTab, selectedSettingsTab, tabsConfig]); + }, [onTabChange, selectedSettingsTab, tabsConfig]); return ( <> @@ -143,6 +143,7 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo( padding-top: ${euiTheme.base * 0.75}px; padding-bottom: ${euiTheme.base * 0.75}px; `} + data-test-subj={`tab-${selectedSettingsTab}`} > {selectedSettingsTab === CONNECTORS_TAB && <ConnectorsSettingsManagement />} {selectedSettingsTab === CONVERSATIONS_TAB && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts index c61a6dda8d235..c753c04fd6e60 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; -export const CONVERSATIONS_TAB = 'CONVERSATIONS_TAB' as const; -export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; -export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; -export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; -export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; -export const EVALUATION_TAB = 'EVALUATION_TAB' as const; +export const CONNECTORS_TAB = 'connectors' as const; +export const CONVERSATIONS_TAB = 'conversations' as const; +export const QUICK_PROMPTS_TAB = 'quick_prompts' as const; +export const SYSTEM_PROMPTS_TAB = 'system_prompts' as const; +export const ANONYMIZATION_TAB = 'anonymization' as const; +export const KNOWLEDGE_BASE_TAB = 'knowledge_base' as const; +export const EVALUATION_TAB = 'evaluation' as const; export const DEFAULT_PAGE_SIZE = 25; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx index b7f33b9a6af5a..3a19a68643006 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx @@ -18,8 +18,11 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; +import { AnonymizationSettingsManagement } from '../../../data_anonymization/settings/anonymization_settings_management'; import { useAssistantContext } from '../../../..'; import * as i18n from '../../assistant_header/translations'; +import { AlertsSettingsModal } from '../alerts_settings/alerts_settings_modal'; +import { KNOWLEDGE_BASE_TAB } from '../const'; interface Params { isDisabled?: boolean; @@ -37,6 +40,15 @@ export const SettingsContextMenu: React.FC<Params> = React.memo( const [isPopoverOpen, setPopover] = useState(false); const [isResetConversationModalVisible, setIsResetConversationModalVisible] = useState(false); + + const [isAlertsSettingsModalVisible, setIsAlertsSettingsModalVisible] = useState(false); + const closeAlertSettingsModal = useCallback(() => setIsAlertsSettingsModalVisible(false), []); + const showAlertSettingsModal = useCallback(() => setIsAlertsSettingsModalVisible(true), []); + + const [isAnonymizationModalVisible, setIsAnonymizationModalVisible] = useState(false); + const closeAnonymizationModal = useCallback(() => setIsAnonymizationModalVisible(false), []); + const showAnonymizationModal = useCallback(() => setIsAnonymizationModalVisible(true), []); + const closeDestroyModal = useCallback(() => setIsResetConversationModalVisible(false), []); const onButtonClick = useCallback(() => { @@ -60,14 +72,24 @@ export const SettingsContextMenu: React.FC<Params> = React.memo( [navigateToApp] ); + const handleNavigateToAnonymization = useCallback(() => { + showAnonymizationModal(); + closePopover(); + }, [closePopover, showAnonymizationModal]); + const handleNavigateToKnowledgeBase = useCallback( () => navigateToApp('management', { - path: 'kibana/securityAiAssistantManagement', + path: `kibana/securityAiAssistantManagement?tab=${KNOWLEDGE_BASE_TAB}`, }), [navigateToApp] ); + const handleShowAlertsModal = useCallback(() => { + showAlertSettingsModal(); + closePopover(); + }, [closePopover, showAlertSettingsModal]); + // We are migrating away from the settings modal in favor of the new Stack Management UI // Currently behind `assistantKnowledgeBaseByDefault` FF const newItems: ReactElement[] = useMemo( @@ -80,14 +102,6 @@ export const SettingsContextMenu: React.FC<Params> = React.memo( > {i18n.AI_ASSISTANT_SETTINGS} </EuiContextMenuItem>, - <EuiContextMenuItem - aria-label={'anonymization'} - onClick={handleNavigateToSettings} - icon={'eye'} - data-test-subj={'anonymization'} - > - {i18n.ANONYMIZATION} - </EuiContextMenuItem>, <EuiContextMenuItem aria-label={'knowledge-base'} onClick={handleNavigateToKnowledgeBase} @@ -96,9 +110,17 @@ export const SettingsContextMenu: React.FC<Params> = React.memo( > {i18n.KNOWLEDGE_BASE} </EuiContextMenuItem>, + <EuiContextMenuItem + aria-label={'anonymization'} + onClick={handleNavigateToAnonymization} + icon={'eye'} + data-test-subj={'anonymization'} + > + {i18n.ANONYMIZATION} + </EuiContextMenuItem>, <EuiContextMenuItem aria-label={'alerts-to-analyze'} - onClick={handleNavigateToSettings} + onClick={handleShowAlertsModal} icon={'magnifyWithExclamation'} data-test-subj={'alerts-to-analyze'} > @@ -112,7 +134,13 @@ export const SettingsContextMenu: React.FC<Params> = React.memo( </EuiFlexGroup> </EuiContextMenuItem>, ], - [handleNavigateToKnowledgeBase, handleNavigateToSettings, knowledgeBase] + [ + handleNavigateToAnonymization, + handleNavigateToKnowledgeBase, + handleNavigateToSettings, + handleShowAlertsModal, + knowledgeBase.latestAlerts, + ] ); const items = useMemo( @@ -164,6 +192,10 @@ export const SettingsContextMenu: React.FC<Params> = React.memo( `} /> </EuiPopover> + {isAlertsSettingsModalVisible && <AlertsSettingsModal onClose={closeAlertSettingsModal} />} + {isAnonymizationModalVisible && ( + <AnonymizationSettingsManagement modalMode onClose={closeAnonymizationModal} /> + )} {isResetConversationModalVisible && ( <EuiConfirmModal title={i18n.RESET_CONVERSATION} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx index 92a2a3df2683b..6e4a114c14256 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -21,7 +21,7 @@ export const SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY = 'systemPromptTable'; export const ANONYMIZATION_TABLE_SESSION_STORAGE_KEY = 'anonymizationTable'; /** The default `n` latest alerts, ordered by risk score, sent as context to the assistant */ -export const DEFAULT_LATEST_ALERTS = 20; +export const DEFAULT_LATEST_ALERTS = 100; /** The default maximum number of alerts to be sent as context when generating Attack discoveries */ export const DEFAULT_ATTACK_DISCOVERY_MAX_ALERTS = 200; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index 2319bf67de89a..9ac817e03973a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -14,7 +14,8 @@ import useLocalStorage from 'react-use/lib/useLocalStorage'; import useSessionStorage from 'react-use/lib/useSessionStorage'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { AssistantFeatures, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; -import { NavigateToAppOptions } from '@kbn/core/public'; +import { NavigateToAppOptions, UserProfileService } from '@kbn/core/public'; +import { useQuery } from '@tanstack/react-query'; import { updatePromptContexts } from './helpers'; import type { PromptContext, @@ -75,6 +76,7 @@ export interface AssistantProviderProps { title?: string; toasts?: IToasts; currentAppId: string; + userProfileService: UserProfileService; } export interface UserAvatar { @@ -108,7 +110,6 @@ export interface UseAssistantContext { registerPromptContext: RegisterPromptContext; selectedSettingsTab: SettingsTabs | null; setAssistantStreamingEnabled: React.Dispatch<React.SetStateAction<boolean | undefined>>; - setCurrentUserAvatar: React.Dispatch<React.SetStateAction<UserAvatar | undefined>>; setKnowledgeBase: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig | undefined>>; setLastConversationId: React.Dispatch<React.SetStateAction<string | undefined>>; setSelectedSettingsTab: React.Dispatch<React.SetStateAction<SettingsTabs | null>>; @@ -126,6 +127,7 @@ export interface UseAssistantContext { unRegisterPromptContext: UnRegisterPromptContext; currentAppId: string; codeBlockRef: React.MutableRefObject<(codeBlock: string) => void>; + userProfileService: UserProfileService; } const AssistantContext = React.createContext<UseAssistantContext | undefined>(undefined); @@ -148,6 +150,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ title = DEFAULT_ASSISTANT_TITLE, toasts, currentAppId, + userProfileService, }) => { /** * Session storage for traceOptions, including APM URL and LangSmith Project/API Key @@ -224,7 +227,18 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ /** * Current User Avatar */ - const [currentUserAvatar, setCurrentUserAvatar] = useState<UserAvatar>(); + const { data: currentUserAvatar } = useQuery({ + queryKey: ['currentUserAvatar'], + queryFn: async () => + userProfileService.getCurrent<{ avatar: UserAvatar }>({ + dataPath: 'avatar', + }), + select: (data) => { + return data.data.avatar; + }, + keepPreviousData: true, + refetchOnWindowFocus: false, + }); /** * Settings State @@ -275,7 +289,6 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ assistantStreamingEnabled: localStorageStreaming ?? true, setAssistantStreamingEnabled: setLocalStorageStreaming, setKnowledgeBase: setLocalStorageKnowledgeBase, - setCurrentUserAvatar, setSelectedSettingsTab, setShowAssistantOverlay, setTraceOptions: setSessionStorageTraceOptions, @@ -289,6 +302,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ baseConversations, currentAppId, codeBlockRef, + userProfileService, }), [ actionTypeRegistry, @@ -323,6 +337,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({ baseConversations, currentAppId, codeBlockRef, + userProfileService, ] ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/types.tsx index dad5ef04e0c18..80996bbf80d68 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -69,6 +69,8 @@ export interface AssistantAvailability { hasConnectorsReadPrivilege: boolean; // When true, user has `Edit` privilege for `AnonymizationFields` hasUpdateAIAssistantAnonymization: boolean; + // When true, user has `Edit` privilege for `Global Knowledge Base` + hasManageGlobalKnowledgeBase: boolean; } export type GetAssistantMessages = (commentArgs: { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.test.tsx index 5465ca19e99de..69e3df940d285 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.test.tsx @@ -20,6 +20,7 @@ describe('connectorMissingCallout', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }; @@ -58,6 +59,7 @@ describe('connectorMissingCallout', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: false, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: false, isAssistantEnabled: true, }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.test.tsx index 191b9c0e3d90b..375d03581cb39 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.test.tsx @@ -78,6 +78,7 @@ const mockUseAssistantContext = { ], assistantAvailability: { hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, }, baseAllow: ['@timestamp', 'event.category', 'user.name'], baseAllowReplacement: ['user.name', 'host.ip'], diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx index 5fca3c6996d2f..bb6ed94f546f0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx @@ -5,7 +5,19 @@ * 2.0. */ -import { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiPanel, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import React, { useCallback, useState } from 'react'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -25,13 +37,23 @@ import { import { useFetchAnonymizationFields } from '../../../assistant/api/anonymization_fields/use_fetch_anonymization_fields'; import { AssistantSettingsBottomBar } from '../../../assistant/settings/assistant_settings_bottom_bar'; import { useAssistantContext } from '../../../assistant_context'; -import { SETTINGS_UPDATED_TOAST_TITLE } from '../../../assistant/settings/translations'; +import { + CANCEL, + SAVE, + SETTINGS_UPDATED_TOAST_TITLE, +} from '../../../assistant/settings/translations'; export interface Props { defaultPageSize?: number; + modalMode?: boolean; + onClose?: () => void; } -const AnonymizationSettingsManagementComponent: React.FC<Props> = ({ defaultPageSize = 5 }) => { +const AnonymizationSettingsManagementComponent: React.FC<Props> = ({ + defaultPageSize = 5, + modalMode = false, + onClose, +}) => { const { toasts } = useAssistantContext(); const { data: anonymizationFields } = useFetchAnonymizationFields(); const [hasPendingChanges, setHasPendingChanges] = useState(false); @@ -52,9 +74,10 @@ const AnonymizationSettingsManagementComponent: React.FC<Props> = ({ defaultPage ); const onCancelClick = useCallback(() => { + onClose?.(); resetSettings(); setHasPendingChanges(false); - }, [resetSettings]); + }, [onClose, resetSettings]); const handleSave = useCallback( async (param?: { callback?: () => void }) => { @@ -71,7 +94,8 @@ const AnonymizationSettingsManagementComponent: React.FC<Props> = ({ defaultPage const onSaveButtonClicked = useCallback(() => { handleSave(); - }, [handleSave]); + onClose?.(); + }, [handleSave, onClose]); const handleAnonymizationFieldsBulkActions = useCallback< UseAnonymizationListUpdateProps['setAnonymizationFieldsBulkActions'] @@ -99,6 +123,47 @@ const AnonymizationSettingsManagementComponent: React.FC<Props> = ({ defaultPage setAnonymizationFieldsBulkActions: handleAnonymizationFieldsBulkActions, setUpdatedAnonymizationData: handleUpdatedAnonymizationData, }); + + if (modalMode) { + return ( + <EuiModal onClose={onCancelClick}> + <EuiModalHeader> + <EuiModalHeaderTitle>{i18n.SETTINGS_TITLE}</EuiModalHeaderTitle> + </EuiModalHeader> + <EuiModalBody> + <EuiText size="m">{i18n.SETTINGS_DESCRIPTION}</EuiText> + + <EuiSpacer size="m" /> + + <EuiFlexGroup alignItems="center" data-test-subj="summary" gutterSize="none"> + <Stats + isDataAnonymizable={true} + anonymizationFields={updatedAnonymizationData.data} + titleSize="m" + gap={euiThemeVars.euiSizeS} + /> + </EuiFlexGroup> + + <EuiSpacer size="m" /> + + <ContextEditor + anonymizationFields={updatedAnonymizationData} + compressed={false} + onListUpdated={onListUpdated} + rawData={null} + pageSize={defaultPageSize} + /> + </EuiModalBody> + <EuiModalFooter> + <EuiButtonEmpty onClick={onCancelClick}>{CANCEL}</EuiButtonEmpty> + <EuiButton type="submit" onClick={onSaveButtonClicked} fill disabled={!hasPendingChanges}> + {SAVE} + </EuiButton> + </EuiModalFooter> + </EuiModal> + ); + } + return ( <> <EuiPanel hasShadow={false} hasBorder paddingSize="l"> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx index 6cfa60eff282d..98a4de601ab98 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/alerts_range.tsx @@ -66,6 +66,7 @@ export const AlertsRange: React.FC<Props> = React.memo( return ( <EuiRange aria-label={ALERTS_RANGE} + fullWidth compressed={compressed} css={css` max-inline-size: ${MAX_ALERTS_RANGE_WIDTH}px; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx index aa873decdcd87..a46ba652574f6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx @@ -36,13 +36,14 @@ const KNOWLEDGE_BASE_INDEX_PATTERN = '.kibana-elastic-ai-assistant-knowledge-bas interface Props { knowledgeBase: KnowledgeBaseConfig; setUpdatedKnowledgeBaseSettings: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>; + modalMode?: boolean; } /** * Knowledge Base Settings -- set up the Knowledge Base and configure RAG on alerts */ export const KnowledgeBaseSettings: React.FC<Props> = React.memo( - ({ knowledgeBase, setUpdatedKnowledgeBaseSettings }) => { + ({ knowledgeBase, setUpdatedKnowledgeBaseSettings, modalMode = false }) => { const { http, toasts } = useAssistantContext(); const { data: kbStatus, isLoading, isFetching } = useKnowledgeBaseStatus({ http }); const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http, toasts }); @@ -113,7 +114,7 @@ export const KnowledgeBaseSettings: React.FC<Props> = React.memo( return ( <> - <EuiTitle size={'s'}> + <EuiTitle size={'s'} data-test-subj="knowledge-base-settings"> <h2> {i18n.SETTINGS_TITLE}{' '} <EuiBetaBadge iconType={'beaker'} label={i18n.SETTINGS_BADGE} size="s" color="hollow" /> @@ -194,10 +195,12 @@ export const KnowledgeBaseSettings: React.FC<Props> = React.memo( <EuiSpacer size="s" /> - <AlertsSettings - knowledgeBase={knowledgeBase} - setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} - /> + {!modalMode && ( + <AlertsSettings + knowledgeBase={knowledgeBase} + setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings} + /> + )} </> ); } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/add_entry_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/add_entry_button.tsx index 5b3ec4562d086..46f9f0cddf6f4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/add_entry_button.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/add_entry_button.tsx @@ -58,6 +58,7 @@ export const AddEntryButton: React.FC<Props> = React.memo( aria-label={i18n.DOCUMENT} key={i18n.DOCUMENT} icon="document" + data-test-subj="addDocument" onClick={handleDocumentClicked} disabled={!isDocumentAvailable} > @@ -67,7 +68,12 @@ export const AddEntryButton: React.FC<Props> = React.memo( return onIndexClicked || onDocumentClicked ? ( <EuiPopover button={ - <EuiButton iconType="arrowDown" iconSide="right" onClick={onButtonClick}> + <EuiButton + data-test-subj="addEntry" + iconType="arrowDown" + iconSide="right" + onClick={onButtonClick} + > <EuiIcon type="plusInCircle" /> {i18n.NEW} </EuiButton> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/document_entry_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/document_entry_editor.tsx index b33f221bfde3b..11d9ac2d62289 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/document_entry_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/document_entry_editor.tsx @@ -21,116 +21,124 @@ import * as i18n from './translations'; interface Props { entry?: DocumentEntry; setEntry: React.Dispatch<React.SetStateAction<Partial<DocumentEntry>>>; + hasManageGlobalKnowledgeBase: boolean; } -export const DocumentEntryEditor: React.FC<Props> = React.memo(({ entry, setEntry }) => { - // Name - const setName = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => - setEntry((prevEntry) => ({ ...prevEntry, name: e.target.value })), - [setEntry] - ); +export const DocumentEntryEditor: React.FC<Props> = React.memo( + ({ entry, setEntry, hasManageGlobalKnowledgeBase }) => { + // Name + const setName = useCallback( + (e: React.ChangeEvent<HTMLInputElement>) => + setEntry((prevEntry) => ({ ...prevEntry, name: e.target.value })), + [setEntry] + ); - // Sharing - const setSharingOptions = useCallback( - (value: string) => - setEntry((prevEntry) => ({ - ...prevEntry, - users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined, - })), - [setEntry] - ); - // TODO: KB-RBAC Disable global option if no RBAC - const sharingOptions = [ - { - value: i18n.SHARING_PRIVATE_OPTION_LABEL, - inputDisplay: ( - <EuiText size={'s'}> - <EuiIcon - color="subdued" - style={{ lineHeight: 'inherit', marginRight: '4px' }} - type="lock" - /> - {i18n.SHARING_PRIVATE_OPTION_LABEL} - </EuiText> - ), - }, - { - value: i18n.SHARING_GLOBAL_OPTION_LABEL, - inputDisplay: ( - <EuiText size={'s'}> - <EuiIcon - color="subdued" - style={{ lineHeight: 'inherit', marginRight: '4px' }} - type="globe" - /> - {i18n.SHARING_GLOBAL_OPTION_LABEL} - </EuiText> - ), - }, - ]; - const selectedSharingOption = - entry?.users?.length === 0 ? sharingOptions[1].value : sharingOptions[0].value; + // Sharing + const setSharingOptions = useCallback( + (value: string) => + setEntry((prevEntry) => ({ + ...prevEntry, + users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined, + })), + [setEntry] + ); + const sharingOptions = [ + { + value: i18n.SHARING_PRIVATE_OPTION_LABEL, + inputDisplay: ( + <EuiText size={'s'}> + <EuiIcon + color="subdued" + style={{ lineHeight: 'inherit', marginRight: '4px' }} + type="lock" + /> + {i18n.SHARING_PRIVATE_OPTION_LABEL} + </EuiText> + ), + }, + { + value: i18n.SHARING_GLOBAL_OPTION_LABEL, + inputDisplay: ( + <EuiText size={'s'}> + <EuiIcon + color="subdued" + style={{ lineHeight: 'inherit', marginRight: '4px' }} + type="globe" + /> + {i18n.SHARING_GLOBAL_OPTION_LABEL} + </EuiText> + ), + disabled: !hasManageGlobalKnowledgeBase, + }, + ]; + const selectedSharingOption = + entry?.users?.length === 0 ? sharingOptions[1].value : sharingOptions[0].value; - // Text / markdown - const setMarkdownValue = useCallback( - (value: string) => { - setEntry((prevEntry) => ({ ...prevEntry, text: value })); - }, - [setEntry] - ); + // Text / markdown + const setMarkdownValue = useCallback( + (value: string) => { + setEntry((prevEntry) => ({ ...prevEntry, text: value })); + }, + [setEntry] + ); - // Required checkbox - const onRequiredKnowledgeChanged = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => { - setEntry((prevEntry) => ({ ...prevEntry, required: e.target.checked })); - }, - [setEntry] - ); + // Required checkbox + const onRequiredKnowledgeChanged = useCallback( + (e: React.ChangeEvent<HTMLInputElement>) => { + setEntry((prevEntry) => ({ ...prevEntry, required: e.target.checked })); + }, + [setEntry] + ); - return ( - <EuiForm> - <EuiFormRow label={i18n.ENTRY_NAME_INPUT_LABEL} fullWidth> - <EuiFieldText - name="name" - placeholder={i18n.ENTRY_NAME_INPUT_PLACEHOLDER} + return ( + <EuiForm> + <EuiFormRow + label={i18n.ENTRY_NAME_INPUT_LABEL} + helpText={i18n.ENTRY_NAME_INPUT_PLACEHOLDER} fullWidth - value={entry?.name} - onChange={setName} - /> - </EuiFormRow> - <EuiFormRow - label={i18n.ENTRY_SHARING_INPUT_LABEL} - helpText={i18n.SHARING_HELP_TEXT} - fullWidth - > - <EuiSuperSelect - options={sharingOptions} - valueOfSelected={selectedSharingOption} - onChange={setSharingOptions} + > + <EuiFieldText + name="name" + data-test-subj="entryNameInput" + fullWidth + value={entry?.name} + onChange={setName} + /> + </EuiFormRow> + <EuiFormRow + label={i18n.ENTRY_SHARING_INPUT_LABEL} + helpText={i18n.SHARING_HELP_TEXT} fullWidth - /> - </EuiFormRow> - <EuiFormRow label={i18n.ENTRY_MARKDOWN_INPUT_TEXT} fullWidth> - <EuiMarkdownEditor - aria-label={i18n.ENTRY_MARKDOWN_INPUT_TEXT} - placeholder="# Title" - value={entry?.text ?? ''} - onChange={setMarkdownValue} - height={400} - initialViewMode={'editing'} - /> - </EuiFormRow> - <EuiFormRow fullWidth helpText={i18n.ENTRY_REQUIRED_KNOWLEDGE_HELP_TEXT}> - <EuiCheckbox - label={i18n.ENTRY_REQUIRED_KNOWLEDGE_CHECKBOX_LABEL} - id="requiredKnowledge" - onChange={onRequiredKnowledgeChanged} - checked={entry?.required ?? false} - /> - </EuiFormRow> - </EuiForm> - ); -}); + > + <EuiSuperSelect + options={sharingOptions} + valueOfSelected={selectedSharingOption} + onChange={setSharingOptions} + fullWidth + /> + </EuiFormRow> + <EuiFormRow label={i18n.ENTRY_MARKDOWN_INPUT_TEXT} fullWidth> + <EuiMarkdownEditor + aria-label={i18n.ENTRY_MARKDOWN_INPUT_TEXT} + data-test-subj="entryMarkdownInput" + placeholder="# Title" + value={entry?.text ?? ''} + onChange={setMarkdownValue} + height={400} + initialViewMode={'editing'} + /> + </EuiFormRow> + <EuiFormRow fullWidth helpText={i18n.ENTRY_REQUIRED_KNOWLEDGE_HELP_TEXT}> + <EuiCheckbox + label={i18n.ENTRY_REQUIRED_KNOWLEDGE_CHECKBOX_LABEL} + id="requiredKnowledge" + onChange={onRequiredKnowledgeChanged} + checked={entry?.required ?? false} + /> + </EuiFormRow> + </EuiForm> + ); + } +); DocumentEntryEditor.displayName = 'DocumentEntryEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/helpers.ts b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/helpers.ts index 75d66a355d781..456eebfaffb57 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/helpers.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/helpers.ts @@ -23,6 +23,10 @@ export const isSystemEntry = ( ); }; +export const isGlobalEntry = ( + entry: KnowledgeBaseEntryResponse +): entry is KnowledgeBaseEntryResponse => entry.users != null && !entry.users.length; + export const isKnowledgeBaseEntryCreateProps = ( entry: unknown ): entry is z.infer<typeof KnowledgeBaseEntryCreateProps> => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.test.tsx new file mode 100644 index 0000000000000..86cc30ea02943 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.test.tsx @@ -0,0 +1,244 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { KnowledgeBaseSettingsManagement } from '.'; +import { useCreateKnowledgeBaseEntry } from '../../assistant/api/knowledge_base/entries/use_create_knowledge_base_entry'; +import { useDeleteKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_delete_knowledge_base_entries'; +import { useFlyoutModalVisibility } from '../../assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; +import { useKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_knowledge_base_entries'; +import { + isKnowledgeBaseSetup, + useKnowledgeBaseStatus, +} from '../../assistant/api/knowledge_base/use_knowledge_base_status'; +import { useSettingsUpdater } from '../../assistant/settings/use_settings_updater/use_settings_updater'; +import { useUpdateKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_update_knowledge_base_entries'; +import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt'; +import { useAssistantContext } from '../../..'; +import { I18nProvider } from '@kbn/i18n-react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const mockContext = { + basePromptContexts: MOCK_QUICK_PROMPTS, + setSelectedSettingsTab: jest.fn(), + http: { + get: jest.fn(), + }, + assistantFeatures: { assistantKnowledgeBaseByDefault: true }, + selectedSettingsTab: null, + assistantAvailability: { + isAssistantEnabled: true, + }, +}; +jest.mock('../../assistant_context'); +jest.mock('../../assistant/api/knowledge_base/entries/use_create_knowledge_base_entry'); +jest.mock('../../assistant/api/knowledge_base/entries/use_update_knowledge_base_entries'); +jest.mock('../../assistant/api/knowledge_base/entries/use_delete_knowledge_base_entries'); + +jest.mock('../../assistant/settings/use_settings_updater/use_settings_updater'); +jest.mock('../../assistant/api/knowledge_base/use_knowledge_base_status'); +jest.mock('../../assistant/api/knowledge_base/entries/use_knowledge_base_entries'); +jest.mock( + '../../assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility' +); +const mockDataViews = { + getIndices: jest.fn().mockResolvedValue([{ name: 'index-1' }, { name: 'index-2' }]), + getFieldsForWildcard: jest.fn().mockResolvedValue([ + { name: 'field-1', esTypes: ['semantic_text'] }, + { name: 'field-2', esTypes: ['text'] }, + { name: 'field-3', esTypes: ['semantic_text'] }, + ]), +} as unknown as DataViewsContract; +const queryClient = new QueryClient(); +const wrapper = (props: { children: React.ReactNode }) => ( + <I18nProvider> + <QueryClientProvider client={queryClient}>{props.children}</QueryClientProvider> + </I18nProvider> +); +describe('KnowledgeBaseSettingsManagement', () => { + const mockData = [ + { id: '1', name: 'Test Entry 1', type: 'document', kbResource: 'user', users: [{ id: 'hi' }] }, + { id: '2', name: 'Test Entry 2', type: 'index', kbResource: 'global', users: [] }, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + (useAssistantContext as jest.Mock).mockImplementation(() => mockContext); + (useSettingsUpdater as jest.Mock).mockReturnValue({ + knowledgeBase: { latestAlerts: 20 }, + setUpdatedKnowledgeBaseSettings: jest.fn(), + resetSettings: jest.fn(), + saveSettings: jest.fn(), + }); + (isKnowledgeBaseSetup as jest.Mock).mockReturnValue(true); + (useKnowledgeBaseStatus as jest.Mock).mockReturnValue({ + data: { + elser_exists: true, + security_labs_exists: true, + index_exists: true, + pipeline_exists: true, + }, + isFetched: true, + }); + (useKnowledgeBaseEntries as jest.Mock).mockReturnValue({ + data: { data: mockData }, + isFetching: false, + refetch: jest.fn(), + }); + (useFlyoutModalVisibility as jest.Mock).mockReturnValue({ + isFlyoutOpen: false, + openFlyout: jest.fn(), + closeFlyout: jest.fn(), + }); + (useCreateKnowledgeBaseEntry as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + }); + (useUpdateKnowledgeBaseEntries as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + }); + (useDeleteKnowledgeBaseEntries as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + }); + }); + it('renders old kb settings when enableKnowledgeBaseByDefault is not enabled', () => { + (useAssistantContext as jest.Mock).mockImplementation(() => ({ + ...mockContext, + assistantFeatures: { + assistantKnowledgeBaseByDefault: false, + }, + })); + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { wrapper }); + + expect(screen.getByTestId('knowledge-base-settings')).toBeInTheDocument(); + }); + it('renders loading spinner when data is not fetched', () => { + (useKnowledgeBaseStatus as jest.Mock).mockReturnValue({ data: {}, isFetched: false }); + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + + expect(screen.getByTestId('spinning')).toBeInTheDocument(); + }); + + it('Prompts user to set up knowledge base when isKbSetup', async () => { + (useKnowledgeBaseStatus as jest.Mock).mockReturnValue({ + data: { + elser_exists: false, + security_labs_exists: false, + index_exists: false, + pipeline_exists: false, + }, + isFetched: true, + }); + (isKnowledgeBaseSetup as jest.Mock).mockReturnValue(false); + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + + expect(screen.getByTestId('setup-knowledge-base-button')).toBeInTheDocument(); + }); + + it('renders knowledge base table with entries', async () => { + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + waitFor(() => { + expect(screen.getByTestId('knowledge-base-entries-table')).toBeInTheDocument(); + expect(screen.getByText('Test Entry 1')).toBeInTheDocument(); + expect(screen.getByText('Test Entry 2')).toBeInTheDocument(); + }); + }); + + it('opens the flyout when add document button is clicked', async () => { + const openFlyoutMock = jest.fn(); + (useFlyoutModalVisibility as jest.Mock).mockReturnValue({ + isFlyoutOpen: false, + openFlyout: openFlyoutMock, + closeFlyout: jest.fn(), + }); + + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + + await waitFor(() => { + fireEvent.click(screen.getByTestId('addEntry')); + }); + await waitFor(() => { + fireEvent.click(screen.getByTestId('addDocument')); + }); + expect(openFlyoutMock).toHaveBeenCalled(); + }); + + it('refreshes table on refresh button click', async () => { + const refetchMock = jest.fn(); + (useKnowledgeBaseEntries as jest.Mock).mockReturnValue({ + data: { data: mockData }, + isFetching: false, + refetch: refetchMock, + }); + + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + + await waitFor(() => { + fireEvent.click(screen.getByTestId('refresh-entries')); + }); + expect(refetchMock).toHaveBeenCalled(); + }); + + it('handles save and cancel actions for the flyout', async () => { + const closeFlyoutMock = jest.fn(); + (useFlyoutModalVisibility as jest.Mock).mockReturnValue({ + isFlyoutOpen: true, + openFlyout: jest.fn(), + closeFlyout: closeFlyoutMock, + }); + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + + await waitFor(() => { + fireEvent.click(screen.getByTestId('addEntry')); + }); + await waitFor(() => { + fireEvent.click(screen.getByTestId('addDocument')); + }); + + expect(screen.getByTestId('flyout')).toBeVisible(); + + await userEvent.type(screen.getByTestId('entryNameInput'), 'hi'); + + await waitFor(() => { + fireEvent.click(screen.getByTestId('cancel-button')); + }); + + expect(closeFlyoutMock).toHaveBeenCalled(); + }); + + it('handles delete confirmation modal actions', async () => { + render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, { + wrapper, + }); + + await waitFor(() => { + fireEvent.click(screen.getAllByTestId('delete-button')[0]); + }); + expect(screen.getByTestId('delete-entry-confirmation')).toBeInTheDocument(); + await waitFor(() => { + fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); + }); + expect(screen.queryByTestId('delete-entry-confirmation')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx index 5cf887ae3375d..b199039b4efae 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx @@ -7,6 +7,7 @@ import { EuiButton, + EuiConfirmModal, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, @@ -52,15 +53,17 @@ import { isSystemEntry, isKnowledgeBaseEntryCreateProps, isKnowledgeBaseEntryResponse, + isGlobalEntry, } from './helpers'; import { useCreateKnowledgeBaseEntry } from '../../assistant/api/knowledge_base/entries/use_create_knowledge_base_entry'; import { useUpdateKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_update_knowledge_base_entries'; -import { SETTINGS_UPDATED_TOAST_TITLE } from '../../assistant/settings/translations'; +import { DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../assistant/settings/translations'; import { KnowledgeBaseConfig } from '../../assistant/types'; import { isKnowledgeBaseSetup, useKnowledgeBaseStatus, } from '../../assistant/api/knowledge_base/use_knowledge_base_status'; +import { CANCEL_BUTTON_TEXT } from '../../assistant/assistant_header/translations'; interface Params { dataViews: DataViewsContract; @@ -69,6 +72,7 @@ interface Params { export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ dataViews }) => { const { assistantFeatures: { assistantKnowledgeBaseByDefault: enableKnowledgeBaseByDefault }, + assistantAvailability: { hasManageGlobalKnowledgeBase }, http, toasts, } = useAssistantContext(); @@ -76,6 +80,8 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d const { data: kbStatus, isFetched } = useKnowledgeBaseStatus({ http }); const isKbSetup = isKnowledgeBaseSetup(kbStatus); + const [deleteKBItem, setDeleteKBItem] = useState<DocumentEntry | IndexEntry | null>(null); + // Only needed for legacy settings management const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } = useSettingsUpdater( @@ -123,24 +129,28 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d useState<Partial<DocumentEntry | IndexEntry | KnowledgeBaseEntryCreateProps>>(); // CRUD API accessors - const { mutate: createEntry, isLoading: isCreatingEntry } = useCreateKnowledgeBaseEntry({ - http, - toasts, - }); - const { mutate: updateEntries, isLoading: isUpdatingEntries } = useUpdateKnowledgeBaseEntries({ + const { mutateAsync: createEntry, isLoading: isCreatingEntry } = useCreateKnowledgeBaseEntry({ http, toasts, }); - const { mutate: deleteEntry, isLoading: isDeletingEntries } = useDeleteKnowledgeBaseEntries({ + const { mutateAsync: updateEntries, isLoading: isUpdatingEntries } = + useUpdateKnowledgeBaseEntries({ + http, + toasts, + }); + const { mutateAsync: deleteEntry, isLoading: isDeletingEntries } = useDeleteKnowledgeBaseEntries({ http, toasts, }); const isModifyingEntry = isCreatingEntry || isUpdatingEntries || isDeletingEntries; // Flyout Save/Cancel Actions - const onSaveConfirmed = useCallback(() => { + const onSaveConfirmed = useCallback(async () => { if (isKnowledgeBaseEntryResponse(selectedEntry)) { - updateEntries([selectedEntry]); + await updateEntries([selectedEntry]); + closeFlyout(); + } else if (isKnowledgeBaseEntryCreateProps(selectedEntry)) { + await createEntry(selectedEntry); closeFlyout(); } else if (isKnowledgeBaseEntryCreateProps(selectedEntry)) { createEntry(selectedEntry); @@ -166,19 +176,19 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d const columns = useMemo( () => getColumns({ - onEntryNameClicked: ({ id }: KnowledgeBaseEntryResponse) => { - const entry = entries.data.find((e) => e.id === id); - setSelectedEntry(entry); - openFlyout(); - }, isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => { - return !isSystemEntry(entry); + return ( + !isSystemEntry(entry) && (isGlobalEntry(entry) ? hasManageGlobalKnowledgeBase : true) + ); }, - onDeleteActionClicked: ({ id }: KnowledgeBaseEntryResponse) => { - deleteEntry({ ids: [id] }); + // Add delete popover + onDeleteActionClicked: (item: KnowledgeBaseEntryResponse) => { + setDeleteKBItem(item); }, isEditEnabled: (entry: KnowledgeBaseEntryResponse) => { - return !isSystemEntry(entry); + return ( + !isSystemEntry(entry) && (isGlobalEntry(entry) ? hasManageGlobalKnowledgeBase : true) + ); }, onEditActionClicked: ({ id }: KnowledgeBaseEntryResponse) => { const entry = entries.data.find((e) => e.id === id); @@ -186,7 +196,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d openFlyout(); }, }), - [deleteEntry, entries.data, getColumns, openFlyout] + [entries.data, getColumns, hasManageGlobalKnowledgeBase, openFlyout] ); // Refresh button @@ -214,6 +224,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d <EuiFlexItem> <EuiButton color={'text'} + data-test-subj={'refresh-entries'} isDisabled={isFetchingEntries} onClick={handleRefreshTable} iconType={'refresh'} @@ -251,6 +262,24 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d : i18n.NEW_INDEX_FLYOUT_TITLE; }, [selectedEntry]); + const sorting = { + sort: { + field: 'name', + direction: 'desc' as const, + }, + }; + + const handleCancelDeleteEntry = useCallback(() => { + setDeleteKBItem(null); + }, [setDeleteKBItem]); + + const handleDeleteEntry = useCallback(async () => { + if (deleteKBItem?.id) { + await deleteEntry({ ids: [deleteKBItem?.id] }); + setDeleteKBItem(null); + } + }, [deleteEntry, deleteKBItem, setDeleteKBItem]); + if (!enableKnowledgeBaseByDefault) { return ( <> @@ -267,13 +296,6 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d ); } - const sorting = { - sort: { - field: 'name', - direction: 'desc' as const, - }, - }; - return ( <> <EuiPanel hasShadow={false} hasBorder paddingSize="l"> @@ -298,9 +320,10 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d <EuiFlexGroup justifyContent="spaceAround"> <EuiFlexItem grow={false}> {!isFetched ? ( - <EuiLoadingSpinner size="l" /> + <EuiLoadingSpinner data-test-subj="spinning" size="l" /> ) : isKbSetup ? ( <EuiInMemoryTable + data-test-subj="knowledge-base-entries-table" columns={columns} items={entries.data ?? []} search={search} @@ -344,7 +367,13 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d onClose={onSaveCancelled} onSaveCancelled={onSaveCancelled} onSaveConfirmed={onSaveConfirmed} - saveButtonDisabled={!isKnowledgeBaseEntryCreateProps(selectedEntry) || isModifyingEntry} // TODO: KB-RBAC disable for global entries if user doesn't have global RBAC + saveButtonDisabled={ + !isKnowledgeBaseEntryCreateProps(selectedEntry) || + (selectedEntry.users != null && + !selectedEntry.users.length && + !hasManageGlobalKnowledgeBase) + } + saveButtonLoading={isModifyingEntry} > <> {selectedEntry?.type === DocumentEntryType.value ? ( @@ -353,6 +382,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d setEntry={ setSelectedEntry as React.Dispatch<React.SetStateAction<Partial<DocumentEntry>>> } + hasManageGlobalKnowledgeBase={hasManageGlobalKnowledgeBase} /> ) : ( <IndexEntryEditor @@ -361,10 +391,27 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d setEntry={ setSelectedEntry as React.Dispatch<React.SetStateAction<Partial<IndexEntry>>> } + hasManageGlobalKnowledgeBase={hasManageGlobalKnowledgeBase} /> )} </> </Flyout> + {deleteKBItem && ( + <EuiConfirmModal + data-test-subj="delete-entry-confirmation" + title={i18n.DELETE_ENTRY_CONFIRMATION_TITLE(deleteKBItem.name)} + onCancel={handleCancelDeleteEntry} + onConfirm={handleDeleteEntry} + cancelButtonText={CANCEL_BUTTON_TEXT} + confirmButtonText={DELETE} + buttonColor="danger" + defaultFocusedButton="cancel" + confirmButtonDisabled={isModifyingEntry} + isLoading={isModifyingEntry} + > + <p>{i18n.DELETE_ENTRY_CONFIRMATION_CONTENT}</p> + </EuiConfirmModal> + )} </> ); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx new file mode 100644 index 0000000000000..d4634cdf4c563 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, fireEvent, waitFor, within } from '@testing-library/react'; +import { IndexEntryEditor } from './index_entry_editor'; +import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { IndexEntry } from '@kbn/elastic-assistant-common'; +import * as i18n from './translations'; + +describe('IndexEntryEditor', () => { + const mockSetEntry = jest.fn(); + const mockDataViews = { + getIndices: jest.fn().mockResolvedValue([{ name: 'index-1' }, { name: 'index-2' }]), + getFieldsForWildcard: jest.fn().mockResolvedValue([ + { name: 'field-1', esTypes: ['semantic_text'] }, + { name: 'field-2', esTypes: ['text'] }, + { name: 'field-3', esTypes: ['semantic_text'] }, + ]), + } as unknown as DataViewsContract; + + const defaultProps = { + dataViews: mockDataViews, + setEntry: mockSetEntry, + hasManageGlobalKnowledgeBase: true, + entry: { + name: 'Test Entry', + index: 'index-1', + field: 'field-1', + description: 'Test Description', + queryDescription: 'Test Query Description', + users: [], + } as unknown as IndexEntry, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders the form fields with initial values', () => { + const { getByDisplayValue } = render(<IndexEntryEditor {...defaultProps} />); + + waitFor(() => { + expect(getByDisplayValue('Test Entry')).toBeInTheDocument(); + expect(getByDisplayValue('Test Description')).toBeInTheDocument(); + expect(getByDisplayValue('Test Query Description')).toBeInTheDocument(); + expect(getByDisplayValue('index-1')).toBeInTheDocument(); + expect(getByDisplayValue('field-1')).toBeInTheDocument(); + }); + }); + + it('updates the name field on change', () => { + const { getByTestId } = render(<IndexEntryEditor {...defaultProps} />); + + waitFor(() => { + const nameInput = getByTestId('entry-name'); + fireEvent.change(nameInput, { target: { value: 'New Entry Name' } }); + }); + + expect(mockSetEntry).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('updates the description field on change', () => { + const { getByTestId } = render(<IndexEntryEditor {...defaultProps} />); + waitFor(() => { + const descriptionInput = getByTestId('entry-description'); + fireEvent.change(descriptionInput, { target: { value: 'New Description' } }); + }); + + expect(mockSetEntry).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('updates the query description field on change', () => { + const { getByTestId } = render(<IndexEntryEditor {...defaultProps} />); + waitFor(() => { + const queryDescriptionInput = getByTestId('query-description'); + fireEvent.change(queryDescriptionInput, { target: { value: 'New Query Description' } }); + }); + + expect(mockSetEntry).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('displays sharing options and updates on selection', async () => { + const { getByTestId } = render(<IndexEntryEditor {...defaultProps} />); + + await waitFor(() => { + fireEvent.click(getByTestId('sharing-select')); + fireEvent.click(getByTestId('sharing-private-option')); + }); + await waitFor(() => { + expect(mockSetEntry).toHaveBeenCalledWith(expect.any(Function)); + }); + }); + + it('fetches index options and updates on selection', async () => { + const { getAllByTestId, getByTestId } = render(<IndexEntryEditor {...defaultProps} />); + + await waitFor(() => expect(mockDataViews.getIndices).toHaveBeenCalled()); + + await waitFor(() => { + fireEvent.click(getByTestId('index-combobox')); + fireEvent.click(getAllByTestId('comboBoxToggleListButton')[0]); + }); + fireEvent.click(getByTestId('index-2')); + + expect(mockSetEntry).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('fetches field options based on selected index and updates on selection', async () => { + const { getByTestId, getAllByTestId } = render(<IndexEntryEditor {...defaultProps} />); + + await waitFor(() => + expect(mockDataViews.getFieldsForWildcard).toHaveBeenCalledWith({ + pattern: 'index-1', + fieldTypes: ['semantic_text'], + }) + ); + + await waitFor(() => { + fireEvent.click(getByTestId('index-combobox')); + fireEvent.click(getAllByTestId('comboBoxToggleListButton')[0]); + }); + fireEvent.click(getByTestId('index-2')); + + await waitFor(() => { + fireEvent.click(getByTestId('entry-combobox')); + }); + + await userEvent.type( + within(getByTestId('entry-combobox')).getByTestId('comboBoxSearchInput'), + 'field-3' + ); + expect(mockSetEntry).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('disables the field combo box if no index is selected', () => { + const { getByRole } = render( + <IndexEntryEditor {...defaultProps} entry={{ ...defaultProps.entry, index: '' }} /> + ); + + waitFor(() => { + expect(getByRole('combobox', { name: i18n.ENTRY_FIELD_PLACEHOLDER })).toBeDisabled(); + }); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx index f5dd2df3bcaac..7475ea55ca5fc 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx @@ -12,9 +12,11 @@ import { EuiFormRow, EuiComboBoxOptionOption, EuiText, + EuiTextArea, EuiIcon, EuiSuperSelect, } from '@elastic/eui'; +import useAsync from 'react-use/lib/useAsync'; import React, { useCallback } from 'react'; import { IndexEntry } from '@kbn/elastic-assistant-common'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; @@ -24,200 +26,270 @@ interface Props { dataViews: DataViewsContract; entry?: IndexEntry; setEntry: React.Dispatch<React.SetStateAction<Partial<IndexEntry>>>; + hasManageGlobalKnowledgeBase: boolean; } -export const IndexEntryEditor: React.FC<Props> = React.memo(({ dataViews, entry, setEntry }) => { - // Name - const setName = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => - setEntry((prevEntry) => ({ ...prevEntry, name: e.target.value })), - [setEntry] - ); - - // Sharing - const setSharingOptions = useCallback( - (value: string) => - setEntry((prevEntry) => ({ - ...prevEntry, - users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined, - })), - [setEntry] - ); - // TODO: KB-RBAC Disable global option if no RBAC - const sharingOptions = [ - { - value: i18n.SHARING_PRIVATE_OPTION_LABEL, - inputDisplay: ( - <EuiText size={'s'}> - <EuiIcon - color="subdued" - style={{ lineHeight: 'inherit', marginRight: '4px' }} - type="lock" - /> - {i18n.SHARING_PRIVATE_OPTION_LABEL} - </EuiText> - ), - }, - { - value: i18n.SHARING_GLOBAL_OPTION_LABEL, - inputDisplay: ( - <EuiText size={'s'}> - <EuiIcon - color="subdued" - style={{ lineHeight: 'inherit', marginRight: '4px' }} - type="globe" - /> - {i18n.SHARING_GLOBAL_OPTION_LABEL} - </EuiText> - ), - }, - ]; - const selectedSharingOption = - entry?.users?.length === 0 ? sharingOptions[1].value : sharingOptions[0].value; - - // Index - // TODO: For index field autocomplete - // const indexOptions = useMemo(() => { - // const indices = await dataViews.getIndices({ - // pattern: e[0]?.value ?? '', - // isRollupIndex: () => false, - // }); - // }, [dataViews]); - const setIndex = useCallback( - async (e: Array<EuiComboBoxOptionOption<string>>) => { - setEntry((prevEntry) => ({ ...prevEntry, index: e[0]?.value })); - }, - [setEntry] - ); - - const onCreateOption = (searchValue: string) => { - const normalizedSearchValue = searchValue.trim().toLowerCase(); - - if (!normalizedSearchValue) { - return; - } - - const newOption: EuiComboBoxOptionOption<string> = { - label: searchValue, - value: searchValue, +export const IndexEntryEditor: React.FC<Props> = React.memo( + ({ dataViews, entry, setEntry, hasManageGlobalKnowledgeBase }) => { + // Name + const setName = useCallback( + (e: React.ChangeEvent<HTMLInputElement>) => + setEntry((prevEntry) => ({ ...prevEntry, name: e.target.value })), + [setEntry] + ); + + // Sharing + const setSharingOptions = useCallback( + (value: string) => + setEntry((prevEntry) => ({ + ...prevEntry, + users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined, + })), + [setEntry] + ); + const sharingOptions = [ + { + 'data-test-subj': 'sharing-private-option', + value: i18n.SHARING_PRIVATE_OPTION_LABEL, + inputDisplay: ( + <EuiText size={'s'}> + <EuiIcon + color="subdued" + style={{ lineHeight: 'inherit', marginRight: '4px' }} + type="lock" + /> + {i18n.SHARING_PRIVATE_OPTION_LABEL} + </EuiText> + ), + }, + { + 'data-test-subj': 'sharing-global-option', + value: i18n.SHARING_GLOBAL_OPTION_LABEL, + inputDisplay: ( + <EuiText size={'s'}> + <EuiIcon + color="subdued" + style={{ lineHeight: 'inherit', marginRight: '4px' }} + type="globe" + /> + {i18n.SHARING_GLOBAL_OPTION_LABEL} + </EuiText> + ), + disabled: !hasManageGlobalKnowledgeBase, + }, + ]; + + const selectedSharingOption = + entry?.users?.length === 0 ? sharingOptions[1].value : sharingOptions[0].value; + + // Index + const indexOptions = useAsync(async () => { + const indices = await dataViews.getIndices({ + pattern: '*', + isRollupIndex: () => false, + }); + + return indices.map((index) => ({ + 'data-test-subj': index.name, + label: index.name, + value: index.name, + })); + }, [dataViews]); + + const fieldOptions = useAsync(async () => { + const fields = await dataViews.getFieldsForWildcard({ + pattern: entry?.index ?? '', + fieldTypes: ['semantic_text'], + }); + + return fields + .filter((field) => field.esTypes?.includes('semantic_text')) + .map((field) => ({ + 'data-test-subj': field.name, + label: field.name, + value: field.name, + })); + }, [entry]); + + const setIndex = useCallback( + async (e: Array<EuiComboBoxOptionOption<string>>) => { + setEntry((prevEntry) => ({ ...prevEntry, index: e[0]?.value })); + }, + [setEntry] + ); + + const onCreateOption = (searchValue: string) => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + + if (!normalizedSearchValue) { + return; + } + + const newOption: EuiComboBoxOptionOption<string> = { + label: searchValue, + value: searchValue, + }; + + setIndex([newOption]); + setField([{ label: '', value: '' }]); }; - setIndex([newOption]); - }; - - // Field - const setField = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => - setEntry((prevEntry) => ({ ...prevEntry, field: e.target.value })), - [setEntry] - ); - - // Description - const setDescription = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => - setEntry((prevEntry) => ({ ...prevEntry, description: e.target.value })), - [setEntry] - ); - - // Query Description - const setQueryDescription = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => - setEntry((prevEntry) => ({ ...prevEntry, queryDescription: e.target.value })), - [setEntry] - ); - - return ( - <EuiForm> - <EuiFormRow label={i18n.ENTRY_NAME_INPUT_LABEL} fullWidth> - <EuiFieldText - name="name" - placeholder={i18n.ENTRY_NAME_INPUT_PLACEHOLDER} - fullWidth - value={entry?.name} - onChange={setName} - /> - </EuiFormRow> - <EuiFormRow - label={i18n.ENTRY_SHARING_INPUT_LABEL} - helpText={i18n.SHARING_HELP_TEXT} - fullWidth - > - <EuiSuperSelect - options={sharingOptions} - valueOfSelected={selectedSharingOption} - onChange={setSharingOptions} - fullWidth - /> - </EuiFormRow> - <EuiFormRow label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL} fullWidth> - <EuiComboBox - aria-label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL} - isClearable={true} - singleSelection={{ asPlainText: true }} - onCreateOption={onCreateOption} + const onCreateFieldOption = (searchValue: string) => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + + if (!normalizedSearchValue) { + return; + } + + const newOption: EuiComboBoxOptionOption<string> = { + label: searchValue, + value: searchValue, + }; + + setField([newOption]); + }; + + // Field + const setField = useCallback( + async (e: Array<EuiComboBoxOptionOption<string>>) => + setEntry((prevEntry) => ({ ...prevEntry, field: e[0]?.value })), + [setEntry] + ); + + // Description + const setDescription = useCallback( + (e: React.ChangeEvent<HTMLTextAreaElement>) => + setEntry((prevEntry) => ({ ...prevEntry, description: e.target.value })), + [setEntry] + ); + + // Query Description + const setQueryDescription = useCallback( + (e: React.ChangeEvent<HTMLTextAreaElement>) => + setEntry((prevEntry) => ({ ...prevEntry, queryDescription: e.target.value })), + [setEntry] + ); + + return ( + <EuiForm> + <EuiFormRow + label={i18n.ENTRY_NAME_INPUT_LABEL} + helpText={i18n.ENTRY_NAME_INPUT_PLACEHOLDER} fullWidth - selectedOptions={ - entry?.index - ? [ - { - label: entry?.index, - value: entry?.index, - }, - ] - : [] - } - onChange={setIndex} - /> - </EuiFormRow> - <EuiFormRow label={i18n.ENTRY_FIELD_INPUT_LABEL} fullWidth> - <EuiFieldText - name="field" - placeholder={i18n.ENTRY_FIELD_PLACEHOLDER} + > + <EuiFieldText + data-test-subj="entry-name" + name="name" + fullWidth + value={entry?.name} + onChange={setName} + /> + </EuiFormRow> + <EuiFormRow + label={i18n.ENTRY_SHARING_INPUT_LABEL} + helpText={i18n.SHARING_HELP_TEXT} fullWidth - value={entry?.field} - onChange={setField} - /> - </EuiFormRow> - <EuiFormRow - label={i18n.ENTRY_DESCRIPTION_INPUT_LABEL} - helpText={i18n.ENTRY_DESCRIPTION_HELP_LABEL} - fullWidth - > - <EuiFieldText - name="description" + > + <EuiSuperSelect + data-test-subj="sharing-select" + options={sharingOptions} + valueOfSelected={selectedSharingOption} + onChange={setSharingOptions} + fullWidth + /> + </EuiFormRow> + <EuiFormRow label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL} fullWidth> + <EuiComboBox + data-test-subj="index-combobox" + aria-label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL} + isClearable={true} + singleSelection={{ asPlainText: true }} + onCreateOption={onCreateOption} + fullWidth + options={indexOptions.value ?? []} + selectedOptions={ + entry?.index + ? [ + { + label: entry?.index, + value: entry?.index, + }, + ] + : [] + } + onChange={setIndex} + /> + </EuiFormRow> + <EuiFormRow label={i18n.ENTRY_FIELD_INPUT_LABEL} fullWidth> + <EuiComboBox + aria-label={i18n.ENTRY_FIELD_PLACEHOLDER} + data-test-subj="entry-combobox" + isClearable={true} + singleSelection={{ asPlainText: true }} + onCreateOption={onCreateFieldOption} + fullWidth + options={fieldOptions.value ?? []} + selectedOptions={ + entry?.field + ? [ + { + label: entry?.field, + value: entry?.field, + }, + ] + : [] + } + onChange={setField} + isDisabled={!entry?.index} + /> + </EuiFormRow> + <EuiFormRow + label={i18n.ENTRY_DESCRIPTION_INPUT_LABEL} + helpText={i18n.ENTRY_DESCRIPTION_HELP_LABEL} fullWidth - value={entry?.description} - onChange={setDescription} - /> - </EuiFormRow> - <EuiFormRow - label={i18n.ENTRY_QUERY_DESCRIPTION_INPUT_LABEL} - helpText={i18n.ENTRY_QUERY_DESCRIPTION_HELP_LABEL} - fullWidth - > - <EuiFieldText - name="description" + > + <EuiTextArea + name="description" + fullWidth + placeholder={i18n.ENTRY_DESCRIPTION_PLACEHOLDER} + data-test-subj="entry-description" + value={entry?.description} + onChange={setDescription} + rows={2} + /> + </EuiFormRow> + <EuiFormRow + label={i18n.ENTRY_QUERY_DESCRIPTION_INPUT_LABEL} + helpText={i18n.ENTRY_QUERY_DESCRIPTION_HELP_LABEL} fullWidth - value={entry?.queryDescription} - onChange={setQueryDescription} - /> - </EuiFormRow> - <EuiFormRow - label={i18n.ENTRY_OUTPUT_FIELDS_INPUT_LABEL} - helpText={i18n.ENTRY_OUTPUT_FIELDS_HELP_LABEL} - fullWidth - > - <EuiComboBox - aria-label={i18n.ENTRY_OUTPUT_FIELDS_INPUT_LABEL} - isClearable={true} - singleSelection={{ asPlainText: true }} - onCreateOption={onCreateOption} + > + <EuiTextArea + name="query_description" + placeholder={i18n.ENTRY_QUERY_DESCRIPTION_PLACEHOLDER} + data-test-subj="query-description" + value={entry?.queryDescription} + onChange={setQueryDescription} + fullWidth + rows={3} + /> + </EuiFormRow> + <EuiFormRow + label={i18n.ENTRY_OUTPUT_FIELDS_INPUT_LABEL} + helpText={i18n.ENTRY_OUTPUT_FIELDS_HELP_LABEL} fullWidth - selectedOptions={[]} - onChange={setIndex} - /> - </EuiFormRow> - </EuiForm> - ); -}); + > + <EuiComboBox + aria-label={i18n.ENTRY_OUTPUT_FIELDS_INPUT_LABEL} + isClearable={true} + singleSelection={{ asPlainText: true }} + onCreateOption={onCreateOption} + fullWidth + selectedOptions={[]} + onChange={setIndex} + /> + </EuiFormRow> + </EuiForm> + ); + } +); IndexEntryEditor.displayName = 'IndexEntryEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/translations.ts index 0cc16089fdaae..077426884eb8a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/translations.ts @@ -212,6 +212,13 @@ export const DELETE_ENTRY_CONFIRMATION_TITLE = (title: string) => } ); +export const DELETE_ENTRY_CONFIRMATION_CONTENT = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.deleteEntryContent', + { + defaultMessage: "You will not be able to recover this knowledge base entry once it's deleted.", + } +); + export const ENTRY_MARKDOWN_INPUT_TEXT = i18n.translate( 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryMarkdownInputText', { @@ -258,8 +265,14 @@ export const ENTRY_DESCRIPTION_INPUT_LABEL = i18n.translate( export const ENTRY_DESCRIPTION_HELP_LABEL = i18n.translate( 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryDescriptionHelpLabel', { - defaultMessage: - 'A description of the type of data in this index and/or when the assistant should look for data here.', + defaultMessage: 'Describe when this custom knowledge should be used during a conversation.', + } +); + +export const ENTRY_DESCRIPTION_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryDescriptionPlaceholder', + { + defaultMessage: 'Use this index to answer any question related to asset information.', } ); @@ -273,7 +286,16 @@ export const ENTRY_QUERY_DESCRIPTION_INPUT_LABEL = i18n.translate( export const ENTRY_QUERY_DESCRIPTION_HELP_LABEL = i18n.translate( 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryQueryDescriptionHelpLabel', { - defaultMessage: 'Any instructions for extracting the search query from the user request.', + defaultMessage: + 'Describe what query should be constructed by the model to retrieve this custom knowledge.', + } +); + +export const ENTRY_QUERY_DESCRIPTION_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryQueryDescriptionPlaceholder', + { + defaultMessage: + 'Key terms to retrieve asset related information, like host names, IP Addresses or cloud objects.', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx index d0038169cd597..67157b3ae7b12 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx @@ -7,21 +7,69 @@ import { EuiAvatar, EuiBadge, EuiBasicTableColumn, EuiIcon, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { FormattedDate } from '@kbn/i18n-react'; import { DocumentEntryType, IndexEntryType, KnowledgeBaseEntryResponse, } from '@kbn/elastic-assistant-common'; + +import useAsync from 'react-use/lib/useAsync'; import { useAssistantContext } from '../../..'; import * as i18n from './translations'; import { BadgesColumn } from '../../assistant/common/components/assistant_settings_management/badges'; import { useInlineActions } from '../../assistant/common/components/assistant_settings_management/inline_actions'; import { isSystemEntry } from './helpers'; +const AuthorColumn = ({ entry }: { entry: KnowledgeBaseEntryResponse }) => { + const { currentUserAvatar, userProfileService } = useAssistantContext(); + + const userProfile = useAsync(async () => { + const profile = await userProfileService?.bulkGet({ uids: new Set([entry.createdBy]) }); + return profile?.[0].user.username; + }, []); + + const userName = useMemo(() => userProfile?.value ?? 'Unknown', [userProfile?.value]); + const badgeItem = isSystemEntry(entry) ? 'Elastic' : userName; + const userImage = isSystemEntry(entry) ? ( + <EuiIcon + type={'logoElastic'} + css={css` + margin-left: 4px; + margin-right: 14px; + `} + /> + ) : currentUserAvatar?.imageUrl != null ? ( + <EuiAvatar + name={userName} + imageUrl={currentUserAvatar.imageUrl} + size={'s'} + color={currentUserAvatar?.color ?? 'subdued'} + css={css` + margin-right: 10px; + `} + /> + ) : ( + <EuiAvatar + name={userName} + initials={currentUserAvatar?.initials} + size={'s'} + color={currentUserAvatar?.color ?? 'subdued'} + css={css` + margin-right: 10px; + `} + /> + ); + return ( + <> + {userImage} + <EuiText size={'s'}>{badgeItem}</EuiText> + </> + ); +}; + export const useKnowledgeBaseTable = () => { - const { currentUserAvatar } = useAssistantContext(); const getActions = useInlineActions<KnowledgeBaseEntryResponse & { isDefault?: undefined }>(); const getIconForEntry = (entry: KnowledgeBaseEntryResponse): string => { @@ -43,13 +91,11 @@ export const useKnowledgeBaseTable = () => { ({ isDeleteEnabled, isEditEnabled, - onEntryNameClicked, onDeleteActionClicked, onEditActionClicked, }: { isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => boolean; isEditEnabled: (entry: KnowledgeBaseEntryResponse) => boolean; - onEntryNameClicked: (entry: KnowledgeBaseEntryResponse) => void; onDeleteActionClicked: (entry: KnowledgeBaseEntryResponse) => void; onEditActionClicked: (entry: KnowledgeBaseEntryResponse) => void; }): Array<EuiBasicTableColumn<KnowledgeBaseEntryResponse>> => { @@ -78,46 +124,7 @@ export const useKnowledgeBaseTable = () => { { name: i18n.COLUMN_AUTHOR, sortable: ({ users }: KnowledgeBaseEntryResponse) => users[0]?.name, - render: (entry: KnowledgeBaseEntryResponse) => { - // TODO: Look up user from `createdBy` id if privileges allow - const userName = entry.users?.[0]?.name ?? 'Unknown'; - const badgeItem = isSystemEntry(entry) ? 'Elastic' : userName; - const userImage = isSystemEntry(entry) ? ( - <EuiIcon - type={'logoElastic'} - css={css` - margin-left: 4px; - margin-right: 14px; - `} - /> - ) : currentUserAvatar?.imageUrl != null ? ( - <EuiAvatar - name={userName} - imageUrl={currentUserAvatar.imageUrl} - size={'s'} - color={currentUserAvatar?.color ?? 'subdued'} - css={css` - margin-right: 10px; - `} - /> - ) : ( - <EuiAvatar - name={userName} - initials={currentUserAvatar?.initials} - size={'s'} - color={currentUserAvatar?.color ?? 'subdued'} - css={css` - margin-right: 10px; - `} - /> - ); - return ( - <> - {userImage} - <EuiText size={'s'}>{badgeItem}</EuiText> - </> - ); - }, + render: (entry: KnowledgeBaseEntryResponse) => <AuthorColumn entry={entry} />, }, { name: i18n.COLUMN_ENTRIES, @@ -157,7 +164,7 @@ export const useKnowledgeBaseTable = () => { }, ]; }, - [currentUserAvatar, getActions] + [getActions] ); return { getColumns }; }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 13e543a02b3b2..763085cca2688 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -14,6 +14,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { UserProfileService } from '@kbn/core/public'; import { AssistantProvider, AssistantProviderProps } from '../../assistant_context'; import { AssistantAvailability } from '../../assistant_context/types'; @@ -31,6 +32,7 @@ export const mockAssistantAvailability: AssistantAvailability = { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }; @@ -82,6 +84,7 @@ export const TestProvidersComponent: React.FC<Props> = ({ navigateToApp={mockNavigateToApp} {...providerContext} currentAppId={'test'} + userProfileService={jest.fn() as unknown as UserProfileService} > {children} </AssistantProvider> diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index 316355f51c537..17b73f1e6dcd0 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -16,6 +16,7 @@ import { ThemeProvider } from 'styled-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Theme } from '@elastic/charts'; +import { UserProfileService } from '@kbn/core/public'; import { DataQualityProvider, DataQualityProviderProps } from '../../data_quality_context'; import { ResultsRollupContext } from '../../contexts/results_rollup_context'; import { IndicesCheckContext } from '../../contexts/indices_check_context'; @@ -48,6 +49,7 @@ const TestExternalProvidersComponent: React.FC<TestExternalProvidersProps> = ({ hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }; const queryClient = new QueryClient({ @@ -81,6 +83,7 @@ const TestExternalProvidersComponent: React.FC<TestExternalProvidersProps> = ({ baseConversations={{}} navigateToApp={mockNavigateToApp} currentAppId={'securitySolutionUI'} + userProfileService={jest.fn() as unknown as UserProfileService} > {children} </AssistantProvider> diff --git a/x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts index f06e6cf55d9ff..d116aa36d21f0 100644 --- a/x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts @@ -48,8 +48,48 @@ const updateAnonymizationSubFeature: SubFeatureConfig = { ], }; +const manageGlobalKnowledgeBaseSubFeature: SubFeatureConfig = { + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.assistant.manageGlobalKnowledgeBaseSubFeatureName', + { + defaultMessage: 'Knowledge Base', + } + ), + description: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.assistant.manageGlobalKnowledgeBaseSubFeatureDescription', + { + defaultMessage: + 'Make changes to any space level (global) custom knowledge base entries. This will also allow users to modify global entries created by other users.', + } + ), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: [`${APP_ID}-manageGlobalKnowledgeBaseAIAssistant`], + id: 'manage_global_knowledge_base', + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.assistant.manageGlobalKnowledgeBaseSubFeatureDetails', + { + defaultMessage: 'Allow Changes to Global Entries', + } + ), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + ui: ['manageGlobalKnowledgeBaseAIAssistant'], + }, + ], + }, + ], +}; + export enum AssistantSubFeatureId { updateAnonymization = 'updateAnonymizationSubFeature', + manageGlobalKnowledgeBase = 'manageGlobalKnowledgeBaseSubFeature', } /** @@ -65,5 +105,6 @@ export const getAssistantBaseKibanaSubFeatureIds = (): AssistantSubFeatureId[] = export const assistantSubFeaturesMap = Object.freeze( new Map<AssistantSubFeatureId, SubFeatureConfig>([ [AssistantSubFeatureId.updateAnonymization, updateAnonymizationSubFeature], + [AssistantSubFeatureId.manageGlobalKnowledgeBase, manageGlobalKnowledgeBaseSubFeature], ]) ); diff --git a/x-pack/packages/security-solution/features/src/assistant/product_feature_config.ts b/x-pack/packages/security-solution/features/src/assistant/product_feature_config.ts index fbac20c6e8b39..67c352afcfed7 100644 --- a/x-pack/packages/security-solution/features/src/assistant/product_feature_config.ts +++ b/x-pack/packages/security-solution/features/src/assistant/product_feature_config.ts @@ -28,6 +28,9 @@ export const assistantDefaultProductFeaturesConfig: Record< ui: ['ai-assistant'], }, }, - subFeatureIds: [AssistantSubFeatureId.updateAnonymization], + subFeatureIds: [ + AssistantSubFeatureId.updateAnonymization, + AssistantSubFeatureId.manageGlobalKnowledgeBase, + ], }, }; diff --git a/x-pack/packages/security-solution/features/src/product_features_keys.ts b/x-pack/packages/security-solution/features/src/product_features_keys.ts index 6000c110d9298..e72e669716c59 100644 --- a/x-pack/packages/security-solution/features/src/product_features_keys.ts +++ b/x-pack/packages/security-solution/features/src/product_features_keys.ts @@ -153,4 +153,5 @@ export enum CasesSubFeatureId { /** Sub-features IDs for Security Assistant */ export enum AssistantSubFeatureId { updateAnonymization = 'updateAnonymizationSubFeature', + manageGlobalKnowledgeBase = 'manageGlobalKnowledgeBaseSubFeature', } diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts index aef66d406bf74..23f73501b1056 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts @@ -171,6 +171,15 @@ export const getUpdateScript = ({ if (params.assignEmpty == true || params.containsKey('text')) { ctx._source.text = params.text; } + if (params.assignEmpty == true || params.containsKey('description')) { + ctx._source.description = params.description; + } + if (params.assignEmpty == true || params.containsKey('field')) { + ctx._source.field = params.field; + } + if (params.assignEmpty == true || params.containsKey('index')) { + ctx._source.index = params.index; + } ctx._source.updated_at = params.updated_at; ctx._source.updated_by = params.updated_by; `, diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts index a13000242dada..64e7b00089c08 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts @@ -54,6 +54,7 @@ import { loadSecurityLabs } from '../../lib/langchain/content_loaders/security_l export interface GetAIAssistantKnowledgeBaseDataClientParams { modelIdOverride?: string; v2KnowledgeBaseEnabled?: boolean; + manageGlobalKnowledgeBaseAIAssistant?: boolean; } interface KnowledgeBaseDataClientParams extends AIAssistantDataClientParams { @@ -63,6 +64,7 @@ interface KnowledgeBaseDataClientParams extends AIAssistantDataClientParams { ingestPipelineResourceName: string; setIsKBSetupInProgress: (isInProgress: boolean) => void; v2KnowledgeBaseEnabled: boolean; + manageGlobalKnowledgeBaseAIAssistant: boolean; } export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { constructor(public readonly options: KnowledgeBaseDataClientParams) { @@ -307,12 +309,16 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { const writer = await this.getWriter(); const changedAt = new Date().toISOString(); const authenticatedUser = this.options.currentUser; - // TODO: KB-RBAC check for when `global:true` if (authenticatedUser == null) { throw new Error( 'Authenticated user not found! Ensure kbDataClient was initialized from a request.' ); } + + if (global && !this.options.manageGlobalKnowledgeBaseAIAssistant) { + throw new Error('User lacks privileges to create global knowledge base entries'); + } + const { errors, docs_created: docsCreated } = await writer.bulk({ documentsToCreate: documents.map((doc) => { // v1 schema has metadata nested in a `metadata` object @@ -521,12 +527,17 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { global?: boolean; }): Promise<KnowledgeBaseEntryResponse | null> => { const authenticatedUser = this.options.currentUser; - // TODO: KB-RBAC check for when `global:true` + if (authenticatedUser == null) { throw new Error( 'Authenticated user not found! Ensure kbDataClient was initialized from a request.' ); } + + if (global && !this.options.manageGlobalKnowledgeBaseAIAssistant) { + throw new Error('User lacks privileges to create global knowledge base entries'); + } + this.options.logger.debug( () => `Creating Knowledge Base Entry:\n ${JSON.stringify(knowledgeBaseEntry, null, 2)}` ); diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts index 4cde64424ed7e..bfdf8b96f44b0 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts @@ -392,6 +392,7 @@ export class AIAssistantService { setIsKBSetupInProgress: this.setIsKBSetupInProgress.bind(this), spaceId: opts.spaceId, v2KnowledgeBaseEnabled: opts.v2KnowledgeBaseEnabled ?? false, + manageGlobalKnowledgeBaseAIAssistant: opts.manageGlobalKnowledgeBaseAIAssistant ?? false, }); } diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts index 51e3d48505ec2..96753bdd690bd 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts @@ -66,7 +66,6 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout logger.debug(() => `Creating KB Entry:\n${JSON.stringify(request.body)}`); const createResponse = await kbDataClient?.createKnowledgeBaseEntry({ knowledgeBaseEntry: request.body, - // TODO: KB-RBAC check, required when users != null as entry will either be created globally if empty global: request.body.users != null && request.body.users.length === 0, }); diff --git a/x-pack/plugins/elastic_assistant/server/routes/request_context_factory.ts b/x-pack/plugins/elastic_assistant/server/routes/request_context_factory.ts index 3a5b8f220eff4..eeb1a5564d1cf 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/request_context_factory.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/request_context_factory.ts @@ -50,7 +50,7 @@ export class RequestContextFactory implements IRequestContextFactory { const { options } = this; const { core } = options; - const [, startPlugins] = await core.getStartServices(); + const [coreStart, startPlugins] = await core.getStartServices(); const coreContext = await context.core; const getSpaceId = (): string => @@ -88,14 +88,24 @@ export class RequestContextFactory implements IRequestContextFactory { // Additionally, modelIdOverride is used here to enable setting up the KB using a different ELSER model, which // is necessary for testing purposes (`pt_tiny_elser`). getAIAssistantKnowledgeBaseDataClient: memoize( - ({ modelIdOverride, v2KnowledgeBaseEnabled = false }) => { + async ({ modelIdOverride, v2KnowledgeBaseEnabled = false }) => { const currentUser = getCurrentUser(); + + const { securitySolutionAssistant } = await coreStart.capabilities.resolveCapabilities( + request, + { + capabilityPath: 'securitySolutionAssistant.*', + } + ); + return this.assistantService.createAIAssistantKnowledgeBaseDataClient({ spaceId: getSpaceId(), logger: this.logger, currentUser, modelIdOverride, v2KnowledgeBaseEnabled, + manageGlobalKnowledgeBaseAIAssistant: + securitySolutionAssistant.manageGlobalKnowledgeBaseAIAssistant as boolean, }); } ), diff --git a/x-pack/plugins/security_solution/kibana.jsonc b/x-pack/plugins/security_solution/kibana.jsonc index 075da90b44a0f..e48a9794b7e5c 100644 --- a/x-pack/plugins/security_solution/kibana.jsonc +++ b/x-pack/plugins/security_solution/kibana.jsonc @@ -71,7 +71,8 @@ "osquery", "savedObjectsTaggingOss", "guidedOnboarding", - "integrationAssistant" + "integrationAssistant", + "serverless" ], "requiredBundles": [ "esUiShared", @@ -87,4 +88,4 @@ "common" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/security_solution/public/assistant/overlay.tsx b/x-pack/plugins/security_solution/public/assistant/overlay.tsx index 145f18875d275..6f0da894dd728 100644 --- a/x-pack/plugins/security_solution/public/assistant/overlay.tsx +++ b/x-pack/plugins/security_solution/public/assistant/overlay.tsx @@ -9,31 +9,13 @@ import { AssistantOverlay as ElasticAssistantOverlay, useAssistantContext, } from '@kbn/elastic-assistant'; -import { useQuery } from '@tanstack/react-query'; -import type { UserAvatar } from '@kbn/elastic-assistant/impl/assistant_context'; -import { useKibana } from '../common/lib/kibana'; export const AssistantOverlay: React.FC = () => { - const { services } = useKibana(); - - const { data: currentUserAvatar } = useQuery({ - queryKey: ['currentUserAvatar'], - queryFn: () => - services.security?.userProfiles.getCurrent<{ avatar: UserAvatar }>({ - dataPath: 'avatar', - }), - select: (data) => { - return data.data.avatar; - }, - keepPreviousData: true, - refetchOnWindowFocus: false, - }); - const { assistantAvailability } = useAssistantContext(); if (!assistantAvailability.hasAssistantPrivilege) { return null; } - return <ElasticAssistantOverlay currentUserAvatar={currentUserAvatar} />; + return <ElasticAssistantOverlay />; }; diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 93c65bb463584..f4161fccbc1c2 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -142,6 +142,7 @@ export const AssistantProvider: FC<PropsWithChildren<unknown>> = ({ children }) storage, triggersActionsUi: { actionTypeRegistry }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + userProfile, } = useKibana().services; const basePath = useBasePath(); @@ -225,6 +226,7 @@ export const AssistantProvider: FC<PropsWithChildren<unknown>> = ({ children }) title={ASSISTANT_TITLE} toasts={toasts} currentAppId={currentAppId ?? 'securitySolutionUI'} + userProfileService={userProfile} > {children} </ElasticAssistantProvider> diff --git a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx index 65a0ab84d3412..a3c14b9154c3f 100644 --- a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx +++ b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; +import { MemoryRouter } from '@kbn/shared-ux-router'; import { ManagementSettings } from './management_settings'; import type { Conversation } from '@kbn/elastic-assistant'; import { @@ -77,6 +78,12 @@ describe('ManagementSettings', () => { securitySolutionAssistant: { 'ai-assistant': false }, }, }, + chrome: { + docTitle: { + change: jest.fn(), + }, + setBreadcrumbs: jest.fn(), + }, data: { dataViews: { getIndices: jest.fn(), @@ -95,9 +102,11 @@ describe('ManagementSettings', () => { }); return render( - <QueryClientProvider client={queryClient}> - <ManagementSettings /> - </QueryClientProvider> + <MemoryRouter> + <QueryClientProvider client={queryClient}> + <ManagementSettings /> + </QueryClientProvider> + </MemoryRouter> ); }; diff --git a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 48d89e02dfc71..d2434e02641ad 100644 --- a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -5,9 +5,11 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { AssistantSettingsManagement } from '@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management'; import type { Conversation } from '@kbn/elastic-assistant'; +import { useSearchParams } from 'react-router-dom-v5-compat'; +import { i18n } from '@kbn/i18n'; import { mergeBaseWithPersistedConversations, useAssistantContext, @@ -16,8 +18,9 @@ import { } from '@kbn/elastic-assistant'; import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation'; import type { FetchConversationsResponse } from '@kbn/elastic-assistant/impl/assistant/api'; -import { useQuery } from '@tanstack/react-query'; -import type { UserAvatar } from '@kbn/elastic-assistant/impl/assistant_context'; +import { SECURITY_AI_SETTINGS } from '@kbn/elastic-assistant/impl/assistant/settings/translations'; +import { CONNECTORS_TAB } from '@kbn/elastic-assistant/impl/assistant/settings/const'; +import type { SettingsTabs } from '@kbn/elastic-assistant/impl/assistant/settings/types'; import { useKibana } from '../../common/lib/kibana'; const defaultSelectedConversationId = WELCOME_CONVERSATION_TITLE; @@ -27,7 +30,6 @@ export const ManagementSettings = React.memo(() => { baseConversations, http, assistantAvailability: { isAssistantEnabled }, - setCurrentUserAvatar, } = useAssistantContext(); const { @@ -38,23 +40,10 @@ export const ManagementSettings = React.memo(() => { }, }, data: { dataViews }, - security, + chrome: { docTitle, setBreadcrumbs }, + serverless, } = useKibana().services; - const { data: currentUserAvatar } = useQuery({ - queryKey: ['currentUserAvatar'], - queryFn: () => - security?.userProfiles.getCurrent<{ avatar: UserAvatar }>({ - dataPath: 'avatar', - }), - select: (d) => { - return d.data.avatar; - }, - keepPreviousData: true, - refetchOnWindowFocus: false, - }); - setCurrentUserAvatar(currentUserAvatar); - const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record<string, Conversation> => mergeBaseWithPersistedConversations(baseConversations, conversationsData), @@ -75,6 +64,67 @@ export const ManagementSettings = React.memo(() => { [conversations, getDefaultConversation] ); + docTitle.change(SECURITY_AI_SETTINGS); + + const [searchParams] = useSearchParams(); + const currentTab = useMemo( + () => (searchParams.get('tab') as SettingsTabs) ?? CONNECTORS_TAB, + [searchParams] + ); + + const handleTabChange = useCallback( + (tab: string) => { + navigateToApp('management', { + path: `kibana/securityAiAssistantManagement?tab=${tab}`, + }); + }, + [navigateToApp] + ); + + useEffect(() => { + if (serverless) { + serverless.setBreadcrumbs([ + { + text: i18n.translate( + 'xpack.securitySolution.assistant.settings.breadcrumb.serverless.security', + { + defaultMessage: 'AI Assistant for Security Settings', + } + ), + }, + ]); + } else { + setBreadcrumbs([ + { + text: i18n.translate( + 'xpack.securitySolution.assistant.settings.breadcrumb.stackManagement', + { + defaultMessage: 'Stack Management', + } + ), + onClick: (e) => { + e.preventDefault(); + navigateToApp('management'); + }, + }, + { + text: i18n.translate('xpack.securitySolution.assistant.settings.breadcrumb.index', { + defaultMessage: 'AI Assistants', + }), + onClick: (e) => { + e.preventDefault(); + navigateToApp('management', { path: '/kibana/aiAssistantManagementSelection' }); + }, + }, + { + text: i18n.translate('xpack.securitySolution.assistant.settings.breadcrumb.security', { + defaultMessage: 'Security', + }), + }, + ]); + } + }, [navigateToApp, serverless, setBreadcrumbs]); + if (!securityAIAssistantEnabled) { navigateToApp('home'); } @@ -84,6 +134,8 @@ export const ManagementSettings = React.memo(() => { <AssistantSettingsManagement selectedConversation={currentConversation} dataViews={dataViews} + onTabChange={handleTabChange} + currentTab={currentTab} /> ); } diff --git a/x-pack/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx b/x-pack/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx index 68b49bb7d28ee..8ad7661abd0bc 100644 --- a/x-pack/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx @@ -20,6 +20,8 @@ export interface UseAssistantAvailability { hasConnectorsReadPrivilege: boolean; // When true, user has `Edit` privilege for `AnonymizationFields` hasUpdateAIAssistantAnonymization: boolean; + // When true, user has `Edit` privilege for `Global Knowledge Base` + hasManageGlobalKnowledgeBase: boolean; } export const useAssistantAvailability = (): UseAssistantAvailability => { @@ -28,6 +30,8 @@ export const useAssistantAvailability = (): UseAssistantAvailability => { const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true; const hasUpdateAIAssistantAnonymization = capabilities[ASSISTANT_FEATURE_ID]?.updateAIAssistantAnonymization === true; + const hasManageGlobalKnowledgeBase = + capabilities[ASSISTANT_FEATURE_ID]?.manageGlobalKnowledgeBaseAIAssistant === true; // Connectors & Actions capabilities as defined in x-pack/plugins/actions/server/feature.ts // `READ` ui capabilities defined as: { ui: ['show', 'execute'] } @@ -45,5 +49,6 @@ export const useAssistantAvailability = (): UseAssistantAvailability => { hasConnectorsReadPrivilege, isAssistantEnabled: isEnterprise, hasUpdateAIAssistantAnonymization, + hasManageGlobalKnowledgeBase, }; }; diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 04860ba9c6c71..56cdc325c9646 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -10,6 +10,7 @@ import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/a import React from 'react'; import type { AssistantAvailability } from '@kbn/elastic-assistant'; import { AssistantProvider } from '@kbn/elastic-assistant'; +import type { UserProfileService } from '@kbn/core/public'; import { BASE_SECURITY_CONVERSATIONS } from '../../assistant/content/conversations'; interface Props { @@ -33,6 +34,7 @@ export const MockAssistantProviderComponent: React.FC<Props> = ({ hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }; @@ -51,6 +53,7 @@ export const MockAssistantProviderComponent: React.FC<Props> = ({ navigateToApp={mockNavigateToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} currentAppId={'test'} + userProfileService={jest.fn() as unknown as UserProfileService} > {children} </AssistantProvider> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx index 23c2d2e7b9f6b..2f07909c0f56a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx @@ -18,6 +18,7 @@ import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { BASE_SECURITY_CONVERSATIONS } from '../../../../assistant/content/conversations'; +import type { UserProfileService } from '@kbn/core-user-profile-browser'; jest.mock('../../../../common/lib/kibana'); @@ -34,6 +35,7 @@ const mockAssistantAvailability: AssistantAvailability = { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }; const queryClient = new QueryClient({ @@ -65,6 +67,7 @@ const ContextWrapper: FC<PropsWithChildren<unknown>> = ({ children }) => ( navigateToApp={mockNavigationToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} currentAppId={'security'} + userProfileService={jest.fn() as unknown as UserProfileService} > {children} </AssistantProvider> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx index 3cecf2b0acfe5..9ca0d9fd18e7d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx @@ -33,6 +33,7 @@ describe('useAssistant', () => { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }); jest @@ -51,6 +52,7 @@ describe('useAssistant', () => { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }); jest @@ -69,6 +71,7 @@ describe('useAssistant', () => { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, hasUpdateAIAssistantAnonymization: true, + hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }); jest diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index b9dd41a80e668..6ac8b349b74c5 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -60,6 +60,7 @@ import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/publ import type { PluginStartContract } from '@kbn/alerting-plugin/public/plugin'; import type { MapsStartApi } from '@kbn/maps-plugin/public'; import type { IntegrationAssistantPluginStart } from '@kbn/integration-assistant-plugin/public'; +import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -154,6 +155,7 @@ export interface StartPlugins { alerting: PluginStartContract; core: CoreStart; integrationAssistant?: IntegrationAssistantPluginStart; + serverless?: ServerlessPluginStart; } export interface StartPluginsDependencies extends StartPlugins { diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index ce79bd061548f..5098a75e00cf2 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -228,5 +228,7 @@ "@kbn/core-saved-objects-server-mocks", "@kbn/core-http-router-server-internal", "@kbn/core-security-server-mocks", + "@kbn/serverless", + "@kbn/core-user-profile-browser", ] } diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts index cb39ae7c661e0..f6f36d6fa7a3f 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts @@ -4,12 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { CardNavExtensionDefinition } from '@kbn/management-cards-navigation'; +import { appCategories, type CardNavExtensionDefinition } from '@kbn/management-cards-navigation'; import { getNavigationPropsFromId, SecurityPageName, ExternalPageName, } from '@kbn/security-solution-navigation'; +import { i18n } from '@kbn/i18n'; import type { Services } from '../common/services'; const SecurityManagementCards = new Map<string, CardNavExtensionDefinition['category']>([ @@ -42,6 +43,12 @@ export const enableManagementCardsLanding = (services: Services) => { {} ); + const securityAiAssistantManagement = getSecurityAiAssistantManagementDefinition(services); + + if (securityAiAssistantManagement) { + cardNavDefinitions.securityAiAssistantManagement = securityAiAssistantManagement; + } + management.setupCardsNavigation({ enabled: true, extendCardNavDefinitions: services.serverless.getNavigationCards( @@ -51,3 +58,29 @@ export const enableManagementCardsLanding = (services: Services) => { }); }); }; + +const getSecurityAiAssistantManagementDefinition = (services: Services) => { + const { application } = services; + const aiAssistantIsEnabled = application.capabilities.securitySolutionAssistant?.['ai-assistant']; + + if (aiAssistantIsEnabled) { + return { + category: appCategories.OTHER, + title: i18n.translate( + 'xpack.securitySolutionServerless.securityAiAssistantManagement.app.title', + { + defaultMessage: 'AI assistant for Security settings', + } + ), + description: i18n.translate( + 'xpack.securitySolutionServerless.securityAiAssistantManagement.app.description', + { + defaultMessage: 'Manage your AI assistant for Security settings.', + } + ), + icon: 'sparkles', + }; + } + + return null; +}; diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 8c95f39fd6e3e..1ff986829415b 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -78,6 +78,7 @@ export default function ({ getService }: FtrProviderContext) { 'minimal_all', 'minimal_read', 'update_anonymization', + 'manage_global_knowledge_base', ], securitySolutionAttackDiscovery: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: [ diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 41fe1e79b7f12..57a166ef4be9d 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -166,6 +166,7 @@ export default function ({ getService }: FtrProviderContext) { 'minimal_all', 'minimal_read', 'update_anonymization', + 'manage_global_knowledge_base', ], securitySolutionAttackDiscovery: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: [ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts b/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts index 81491abd85f81..5f030c61de65a 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts @@ -42,7 +42,6 @@ import { QUICK_PROMPT_BADGE, ADD_NEW_CONNECTOR, SHOW_ANONYMIZED_BUTTON, - ASSISTANT_SETTINGS_BUTTON, SEND_TO_TIMELINE_BUTTON, } from '../screens/ai_assistant'; import { TOASTER } from '../screens/alerts_detection_rules'; @@ -224,5 +223,4 @@ export const assertConversationReadOnly = () => { cy.get(CHAT_CONTEXT_MENU).should('be.disabled'); cy.get(FLYOUT_NAV_TOGGLE).should('be.disabled'); cy.get(NEW_CHAT).should('be.disabled'); - cy.get(ASSISTANT_SETTINGS_BUTTON).should('be.disabled'); }; From 8787fd81126ee977e8077ed2ef86e3032804b4c8 Mon Sep 17 00:00:00 2001 From: Ido Cohen <90558359+CohenIdo@users.noreply.github.com> Date: Wed, 16 Oct 2024 08:59:59 +0300 Subject: [PATCH 83/84] Report agentless uage via telemetry --- .../telemetry/collectors/installation_stats_collector.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/installation_stats_collector.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/installation_stats_collector.ts index d4e4e910a50b7..8d30daa1fb141 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/installation_stats_collector.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/installation_stats_collector.ts @@ -13,7 +13,6 @@ import { SO_SEARCH_LIMIT, } from '@kbn/fleet-plugin/common'; import { agentPolicyService } from '@kbn/fleet-plugin/server/services'; -import { AGENTLESS_POLICY_ID } from '@kbn/fleet-plugin/common/constants'; import type { CloudbeatConfigKeyType, CloudSecurityInstallationStats, @@ -100,10 +99,12 @@ const getInstalledPackagePolicies = ( const installationStats = packagePolicies.flatMap( (packagePolicy: PackagePolicy): CloudSecurityInstallationStats[] => packagePolicy.policy_ids.map((agentPolicyId) => { - const agentCounts = - agentPolicies?.find((agentPolicy) => agentPolicy?.id === agentPolicyId)?.agents ?? 0; + const matchedAgentPolicy = agentPolicies?.find( + (agentPolicy) => agentPolicy?.id === agentPolicyId + ); - const isAgentless = agentPolicyId === AGENTLESS_POLICY_ID; + const agentCounts = matchedAgentPolicy?.agents || 0; + const isAgentless = !!matchedAgentPolicy?.supports_agentless; const isSetupAutomatic = getEnabledIsSetupAutomatic(packagePolicy); From e47099924b2d993387bf33ba59210cad22d394f0 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani <marcoantonio.ghiani01@gmail.com> Date: Wed, 16 Oct 2024 09:05:49 +0200 Subject: [PATCH 84/84] [Dataset Quality] Fix project view breadcrumbs (#196281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #195734 These changes fix the project navigation breadcrumbs by making the result consistent between the different navigation modes. Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co> --- .../public/routes/dataset_quality/index.tsx | 5 ++- .../dataset_quality_details/context.tsx | 29 ++++----------- .../public/utils/use_breadcrumbs.tsx | 36 +++++++++++-------- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/data_quality/public/routes/dataset_quality/index.tsx b/x-pack/plugins/data_quality/public/routes/dataset_quality/index.tsx index 7ef7c17669e3d..65dae1ec45a81 100644 --- a/x-pack/plugins/data_quality/public/routes/dataset_quality/index.tsx +++ b/x-pack/plugins/data_quality/public/routes/dataset_quality/index.tsx @@ -9,7 +9,6 @@ import { EuiEmptyPrompt, EuiLoadingLogo } from '@elastic/eui'; import type { DatasetQualityController } from '@kbn/dataset-quality-plugin/public/controller/dataset_quality'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { PLUGIN_NAME } from '../../../common'; import { useKbnUrlStateStorageFromRouterContext } from '../../utils/kbn_url_state_context'; import { useBreadcrumbs } from '../../utils/use_breadcrumbs'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; @@ -18,10 +17,10 @@ import { DatasetQualityContextProvider, useDatasetQualityContext } from './conte export const DatasetQualityRoute = () => { const urlStateStorageContainer = useKbnUrlStateStorageFromRouterContext(); const { - services: { chrome, datasetQuality, notifications, appParams }, + services: { datasetQuality, notifications }, } = useKibanaContextForPlugin(); - useBreadcrumbs([{ text: PLUGIN_NAME }], appParams, chrome); + useBreadcrumbs(); return ( <DatasetQualityContextProvider diff --git a/x-pack/plugins/data_quality/public/routes/dataset_quality_details/context.tsx b/x-pack/plugins/data_quality/public/routes/dataset_quality_details/context.tsx index f9af4a38feac6..462cbbbd9288b 100644 --- a/x-pack/plugins/data_quality/public/routes/dataset_quality_details/context.tsx +++ b/x-pack/plugins/data_quality/public/routes/dataset_quality_details/context.tsx @@ -9,16 +9,14 @@ import { IToasts } from '@kbn/core-notifications-browser'; import { DatasetQualityPluginStart } from '@kbn/dataset-quality-plugin/public'; import { DatasetQualityDetailsController } from '@kbn/dataset-quality-plugin/public/controller/dataset_quality_details'; import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; -import { useKibanaContextForPlugin } from '../../utils/use_kibana'; import { getBreadcrumbValue, useBreadcrumbs } from '../../utils/use_breadcrumbs'; import { getDatasetQualityDetailsStateFromUrl, updateUrlFromDatasetQualityDetailsState, } from './url_state_storage_service'; -import { PLUGIN_ID, PLUGIN_NAME } from '../../../common'; const DatasetQualityDetailsContext = createContext<{ controller?: DatasetQualityDetailsController; @@ -39,21 +37,10 @@ export function DatasetQualityDetailsContextProvider({ }: ContextProps) { const [controller, setController] = useState<DatasetQualityDetailsController>(); const history = useHistory(); - const { - services: { - chrome, - appParams, - application: { navigateToApp }, - }, - } = useKibanaContextForPlugin(); - const rootBreadCrumb = useMemo( - () => ({ - text: PLUGIN_NAME, - onClick: () => navigateToApp('management', { path: `/data/${PLUGIN_ID}` }), - }), - [navigateToApp] - ); - const [breadcrumbs, setBreadcrumbs] = useState<ChromeBreadcrumb[]>([rootBreadCrumb]); + + const [breadcrumbs, setBreadcrumbs] = useState<ChromeBreadcrumb[]>([]); + + useBreadcrumbs(breadcrumbs); useEffect(() => { async function getDatasetQualityDetailsController() { @@ -88,7 +75,7 @@ export function DatasetQualityDetailsContextProvider({ datasetQualityDetailsState: state, }); const breadcrumbValue = getBreadcrumbValue(state.dataStream, state.integration); - setBreadcrumbs([rootBreadCrumb, { text: breadcrumbValue }]); + setBreadcrumbs([{ text: breadcrumbValue }]); } ); @@ -99,9 +86,7 @@ export function DatasetQualityDetailsContextProvider({ } getDatasetQualityDetailsController(); - }, [datasetQuality, history, rootBreadCrumb, toastsService, urlStateStorageContainer]); - - useBreadcrumbs(breadcrumbs, appParams, chrome); + }, [datasetQuality, history, toastsService, urlStateStorageContainer]); return ( <DatasetQualityDetailsContext.Provider value={{ controller }}> diff --git a/x-pack/plugins/data_quality/public/utils/use_breadcrumbs.tsx b/x-pack/plugins/data_quality/public/utils/use_breadcrumbs.tsx index b4e6144f3fbac..aaab21f15659e 100644 --- a/x-pack/plugins/data_quality/public/utils/use_breadcrumbs.tsx +++ b/x-pack/plugins/data_quality/public/utils/use_breadcrumbs.tsx @@ -5,28 +5,36 @@ * 2.0. */ -import type { ChromeBreadcrumb, ChromeStart } from '@kbn/core-chrome-browser'; +import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; import { useEffect } from 'react'; -import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import { Integration } from '@kbn/dataset-quality-plugin/common/data_streams_stats/integration'; import { indexNameToDataStreamParts } from '@kbn/dataset-quality-plugin/common'; +import { DATA_QUALITY_LOCATOR_ID, DataQualityLocatorParams } from '@kbn/deeplinks-observability'; +import { PLUGIN_NAME } from '../../common'; +import { useKibanaContextForPlugin } from './use_kibana'; -export const useBreadcrumbs = ( - breadcrumbs: ChromeBreadcrumb[], - params: ManagementAppMountParams, - chromeService: ChromeStart -) => { - const { docTitle } = chromeService; - const isMultiple = breadcrumbs.length > 1; +export const useBreadcrumbs = (breadcrumbs: ChromeBreadcrumb[] = []) => { + const { + services: { appParams, chrome, share }, + } = useKibanaContextForPlugin(); - const docTitleValue = isMultiple ? breadcrumbs[breadcrumbs.length - 1].text : breadcrumbs[0].text; + useEffect(() => { + const locator = share.url.locators.get<DataQualityLocatorParams>(DATA_QUALITY_LOCATOR_ID); - docTitle.change(docTitleValue as string); + const composedBreadcrumbs: ChromeBreadcrumb[] = [ + { + text: PLUGIN_NAME, + deepLinkId: 'management:data_quality', + onClick: () => locator?.navigate({}), + }, + ...breadcrumbs, + ]; - useEffect(() => { - params.setBreadcrumbs(breadcrumbs); - }, [breadcrumbs, params]); + chrome.docTitle.change(composedBreadcrumbs.at(-1)!.text as string); + + appParams.setBreadcrumbs(composedBreadcrumbs); + }, [appParams, breadcrumbs, chrome, share]); }; export const getBreadcrumbValue = (dataStream: string, integration?: Integration) => {